java/Postak/src/cz/frantovo/postak/Postak.java
changeset 19 8d1b4d36c88d
parent 11 c1246cbb7f4c
     1.1 --- a/java/Postak/src/cz/frantovo/postak/Postak.java	Tue Nov 29 17:13:30 2011 +0100
     1.2 +++ b/java/Postak/src/cz/frantovo/postak/Postak.java	Wed May 09 18:10:02 2012 +0200
     1.3 @@ -1,6 +1,8 @@
     1.4  package cz.frantovo.postak;
     1.5  
     1.6  import java.io.File;
     1.7 +import java.io.StringReader;
     1.8 +import java.io.StringWriter;
     1.9  import java.util.ArrayList;
    1.10  import java.util.Collection;
    1.11  import java.util.Properties;
    1.12 @@ -13,210 +15,251 @@
    1.13  import javax.mail.PasswordAuthentication;
    1.14  import javax.mail.Session;
    1.15  import javax.mail.Transport;
    1.16 +import javax.mail.internet.MimeBodyPart;
    1.17  import javax.mail.internet.MimeMessage;
    1.18 +import javax.mail.internet.MimeMultipart;
    1.19 +import javax.xml.transform.Transformer;
    1.20 +import javax.xml.transform.TransformerFactory;
    1.21 +import javax.xml.transform.stream.StreamResult;
    1.22 +import javax.xml.transform.stream.StreamSource;
    1.23  
    1.24  /**
    1.25   * Odešle hromadnou zprávu pomocí SMTP.
    1.26 - * 
    1.27 + *
    1.28   * @author fiki
    1.29   */
    1.30 -public class Postak {   
    1.31 -    
    1.32 -    private static final Logger log = Logger.getLogger(Postak.class.getName());
    1.33 -    /** Regulární výraz pro správnou e-mailovou adresu */
    1.34 -    private static String REGULARNI_EMAIL = "^[_a-zA-Z0-9\\.\\-]+@[_a-zA-Z0-9\\.\\-]+\\.[a-zA-Z]{2,4}$";
    1.35 -    
    1.36 -    private Nastaveni nastaveni;
    1.37 +public class Postak {
    1.38  
    1.39 -    public Postak(Nastaveni nastaveni) {
    1.40 -        this.nastaveni = nastaveni;
    1.41 -    }
    1.42 +	private static final String KÓDOVÁNÍ = "UTF-8";
    1.43 +	private static final Logger log = Logger.getLogger(Postak.class.getName());
    1.44 +	/** Regulární výraz pro správnou e-mailovou adresu */
    1.45 +	private static String REGULARNI_EMAIL = "^[_a-zA-Z0-9\\.\\-]+@[_a-zA-Z0-9\\.\\-]+\\.[a-zA-Z]{2,4}$";
    1.46 +	private Nastaveni nastaveni;
    1.47 +	private TransformerFactory transformerFactory;
    1.48  
    1.49 -    public void setNastaveni(Nastaveni nastaveni) {
    1.50 -        this.nastaveni = nastaveni;
    1.51 -    }
    1.52 +	public Postak(Nastaveni nastaveni) {
    1.53 +		this.nastaveni = nastaveni;
    1.54 +		transformerFactory = TransformerFactory.newInstance();
    1.55 +	}
    1.56  
    1.57 -    /** 
    1.58 -     * Nízkoúrovňová odesílací metoda, která už nekontroluje limit příjemců.
    1.59 -     * Pokud se nevejdou do limitu SMTP serveru, vyhazuje výjimku.
    1.60 -     * 
    1.61 -     * TODO: lepší to bude nestaické - nastavení si vyžádat v konstruktoru
    1.62 -     */
    1.63 -    private void odesliSMTP(HromadnaZprava zprava) throws MessagingException {
    1.64 +	public void setNastaveni(Nastaveni nastaveni) {
    1.65 +		this.nastaveni = nastaveni;
    1.66 +	}
    1.67  
    1.68 -        if (zkontrolujZpravu(zprava) && zkontrolujNastaveni(nastaveni)) {
    1.69 +	/**
    1.70 +	 * Nízkoúrovňová odesílací metoda, která už nekontroluje limit příjemců.
    1.71 +	 * Pokud se nevejdou do limitu SMTP serveru, vyhazuje výjimku.
    1.72 +	 *
    1.73 +	 * TODO: lepší to bude nestaické - nastavení si vyžádat v konstruktoru
    1.74 +	 */
    1.75 +	private void odesliSMTP(HromadnaZprava zprava) throws MessagingException {
    1.76  
    1.77 -            /** Inicializace SMTP */
    1.78 -            Properties smtpVlastnosti = System.getProperties();
    1.79 -            smtpVlastnosti.put("mail.smtp.host", nastaveni.getPostovniServer());
    1.80 -            smtpVlastnosti.put("mail.smtp.port", String.valueOf(nastaveni.getPostovniPort()));
    1.81 +		if (zkontrolujZpravu(zprava) && zkontrolujNastaveni(nastaveni)) {
    1.82  
    1.83 -            if (nastaveni.getPostovniPort() == 465) {
    1.84 -                if (new File(nastaveni.getCestaKCertifikatum()).exists()) {
    1.85 -                    System.setProperty("javax.net.ssl.trustStore", nastaveni.getCestaKCertifikatum());
    1.86 -                    log.log(Level.INFO, "Používám vlastní důvěryhodné certifikáty: {0}", nastaveni.getCestaKCertifikatum());
    1.87 -                }
    1.88 -                /** TODO: neřídit se číslem portu, ale přidat příznak pro šifrování */
    1.89 -                smtpVlastnosti.put("mail.smtp.starttls.enable", "true");
    1.90 -                smtpVlastnosti.put("mail.smtp.socketFactory.class", "javax.net.ssl.SSLSocketFactory");
    1.91 -                smtpVlastnosti.put("mail.smtp.socketFactory.port", String.valueOf(nastaveni.getPostovniPort()));
    1.92 -                smtpVlastnosti.put("mail.smtp.socketFactory.fallback", "false");
    1.93 -            /**
    1.94 -             * NAHRÁNÍ CERTIFIKÁTU:
    1.95 -             * 1) stáhneme si certifikát (---BEGIN CERTIFICATE---) a uložíme do vse_ca.cer             
    1.96 -             * 2) keytool -importcert -file vse_ca.cer -keystore DuveryhodneCertifikaty.keystore -storepass "changeit"
    1.97 -             * Pokud daný soubor existuje, program ho používá, pokud ne, používá certifikáty uložené v systému (Javovské).
    1.98 -             */
    1.99 -            }
   1.100 +			/** Inicializace SMTP */
   1.101 +			Properties smtpVlastnosti = System.getProperties();
   1.102 +			smtpVlastnosti.put("mail.smtp.host", nastaveni.getPostovniServer());
   1.103 +			smtpVlastnosti.put("mail.smtp.port", String.valueOf(nastaveni.getPostovniPort()));
   1.104  
   1.105 -            PostakuvHeslovnik heslovnik = new PostakuvHeslovnik();
   1.106 -            if (nastaveni.getPostovniJmeno() != null && nastaveni.getPostovniJmeno().length() > 0) {
   1.107 -                heslovnik.setJmenoHeslo(nastaveni.getPostovniJmeno(), nastaveni.getPostovniHeslo());
   1.108 -                smtpVlastnosti.put("mail.smtp.auth", "true");
   1.109 -                log.log(Level.FINEST, "Používám pro SMTP jméno a heslo");
   1.110 -            }
   1.111 +			if (nastaveni.getPostovniPort() == 465) {
   1.112 +				if (new File(nastaveni.getCestaKCertifikatum()).exists()) {
   1.113 +					System.setProperty("javax.net.ssl.trustStore", nastaveni.getCestaKCertifikatum());
   1.114 +					log.log(Level.INFO, "Používám vlastní důvěryhodné certifikáty: {0}", nastaveni.getCestaKCertifikatum());
   1.115 +				}
   1.116 +				/** TODO: neřídit se číslem portu, ale přidat příznak pro šifrování */
   1.117 +				smtpVlastnosti.put("mail.smtp.starttls.enable", "true");
   1.118 +				smtpVlastnosti.put("mail.smtp.socketFactory.class", "javax.net.ssl.SSLSocketFactory");
   1.119 +				smtpVlastnosti.put("mail.smtp.socketFactory.port", String.valueOf(nastaveni.getPostovniPort()));
   1.120 +				smtpVlastnosti.put("mail.smtp.socketFactory.fallback", "false");
   1.121 +				/**
   1.122 +				 * NAHRÁNÍ CERTIFIKÁTU:
   1.123 +				 * 1) stáhneme si certifikát (---BEGIN CERTIFICATE---) a uložíme do vse_ca.cer
   1.124 +				 * 2) keytool -importcert -file vse_ca.cer -keystore DuveryhodneCertifikaty.keystore
   1.125 +				 * -storepass "changeit"
   1.126 +				 * Pokud daný soubor existuje, program ho používá, pokud ne, používá certifikáty
   1.127 +				 * uložené v systému (Javovské).
   1.128 +				 */
   1.129 +			}
   1.130  
   1.131 -            Session smtpRelace = Session.getInstance(smtpVlastnosti, heslovnik);
   1.132 +			PostakuvHeslovnik heslovnik = new PostakuvHeslovnik();
   1.133 +			if (nastaveni.getPostovniJmeno() != null && nastaveni.getPostovniJmeno().length() > 0) {
   1.134 +				heslovnik.setJmenoHeslo(nastaveni.getPostovniJmeno(), nastaveni.getPostovniHeslo());
   1.135 +				smtpVlastnosti.put("mail.smtp.auth", "true");
   1.136 +				log.log(Level.FINEST, "Používám pro SMTP jméno a heslo");
   1.137 +			}
   1.138  
   1.139 -            smtpRelace.setDebug(true);
   1.140 -            smtpRelace.setDebugOut(System.out);
   1.141 +			Session smtpRelace = Session.getInstance(smtpVlastnosti, heslovnik);
   1.142  
   1.143 -            /** Sestavení zprávy */
   1.144 -            MimeMessage mimeZprava = new MimeMessage(smtpRelace);
   1.145 -            mimeZprava.setFrom(zprava.getOdesilatel());
   1.146 -            if (zprava.getOdpovedetKomu() != null) {
   1.147 -                Address[] odpovedetKomu = {zprava.getOdpovedetKomu()};
   1.148 -                mimeZprava.setReplyTo(odpovedetKomu);
   1.149 -            }
   1.150 -            naplnPrijemce(mimeZprava, zprava);
   1.151 -            mimeZprava.setSubject(zprava.getPredmet());
   1.152 -            mimeZprava.setHeader("User-Agent", "https://frantovo.cz/projekty/SuperPostak/");
   1.153 -            mimeZprava.setHeader("Precedence", "bulk");
   1.154 -            if (zprava.isFormatHTML()) {
   1.155 -                mimeZprava.setText(zprava.getText(), "UTF-8", "html");
   1.156 -            } else {
   1.157 -                mimeZprava.setText(zprava.getText(), "UTF-8");
   1.158 -            }
   1.159 +			smtpRelace.setDebug(true);
   1.160 +			smtpRelace.setDebugOut(System.out);
   1.161  
   1.162 -            /** Vlastní odeslání */
   1.163 -            Transport.send(mimeZprava);
   1.164 +			/** Sestavení zprávy */
   1.165 +			MimeMessage mimeZprava = new MimeMessage(smtpRelace);
   1.166 +			mimeZprava.setFrom(zprava.getOdesilatel());
   1.167 +			if (zprava.getOdpovedetKomu() != null) {
   1.168 +				Address[] odpovedetKomu = {zprava.getOdpovedetKomu()};
   1.169 +				mimeZprava.setReplyTo(odpovedetKomu);
   1.170 +			}
   1.171 +			naplnPrijemce(mimeZprava, zprava);
   1.172 +			mimeZprava.setSubject(zprava.getPredmet());
   1.173 +			mimeZprava.setHeader("User-Agent", "https://frantovo.cz/projekty/SuperPostak/");
   1.174 +			mimeZprava.setHeader("Precedence", "bulk");
   1.175  
   1.176 -        } else {
   1.177 -            MessagingException e = new MessagingException("Zpráva nebo nastavení jsou nevyhovující");
   1.178 -            log.log(Level.SEVERE, null, e);
   1.179 -            throw e;
   1.180 -        }
   1.181 +			switch (zprava.getFormát()) {
   1.182 +				case PROSTÝ_TEXT:
   1.183 +					mimeZprava.setText(zprava.getText(), KÓDOVÁNÍ);
   1.184 +					break;
   1.185 +				case XHTML:
   1.186 +					mimeZprava.setText(zprava.getText(), KÓDOVÁNÍ, "html");
   1.187 +					break;
   1.188 +				case OBOJE:
   1.189 +					MimeMultipart multipart = new MimeMultipart("alternative");
   1.190 +					mimeZprava.setContent(multipart);
   1.191  
   1.192 -    }
   1.193 +					MimeBodyPart textováČást = new MimeBodyPart();
   1.194 +					MimeBodyPart xhtmlČást = new MimeBodyPart();
   1.195  
   1.196 -    /**
   1.197 -     * Nastaví zprávě (MimeMessage) všechny příjemce, které najde ve zprávě a nastavení.
   1.198 -     * Respektuje typy příjemců: komu, kopie, skrytá kopie.    
   1.199 -     */
   1.200 -    private static void naplnPrijemce(MimeMessage mimeZprava, HromadnaZprava zprava) throws MessagingException {
   1.201 -        /** 
   1.202 -         * Příjemci se budou definovat pouze ve zprávě.
   1.203 -         * Z nastavení se brát nebudou (výchozí příjemci už ve zprávě budou).
   1.204 -         */
   1.205 -        ArrayList<InternetAddressKomu> prijemci = zprava.getPrijemci();
   1.206 -        for (InternetAddressKomu komu : prijemci) {
   1.207 -            if (zkontrolujAdresu(komu)) {
   1.208 -                mimeZprava.addRecipient(komu.getTyp(), komu);
   1.209 -            }
   1.210 -        }
   1.211 -    }
   1.212 +					textováČást.setText(xhtmlNaProstýText(zprava.getText()), KÓDOVÁNÍ);
   1.213 +					xhtmlČást.setContent(zprava.getText(), "text/html; charset=" + KÓDOVÁNÍ);
   1.214  
   1.215 -    /** Vypíše do logu seznam příjemců */
   1.216 -    public static void vypisPrijemce(Collection<InternetAddressKomu> prijemci) {
   1.217 -        for (InternetAddressKomu p : prijemci) {
   1.218 -            log.log(Level.INFO, p.toString());
   1.219 -        }
   1.220 -    }
   1.221 +					multipart.addBodyPart(textováČást);
   1.222 +					multipart.addBodyPart(xhtmlČást);
   1.223 +					break;
   1.224 +			}
   1.225  
   1.226 -    /** Veřejná odesílací metoda.
   1.227 -     * Postará se o rozdělení zpráv na dílčí (které se vejdou do limitu příjemců)
   1.228 -     */
   1.229 -    public void odesli(HromadnaZprava zprava) throws MessagingException {
   1.230 -        Collection<HromadnaZprava> zpravy = zprava.getDilciZpravy(nastaveni.getLimitZprav());
   1.231 +			/** Vlastní odeslání */
   1.232 +			Transport.send(mimeZprava);
   1.233  
   1.234 -        for (HromadnaZprava dilciZprava : zpravy) {
   1.235 -            odesliSMTP(dilciZprava);
   1.236 -        }
   1.237 -    }
   1.238 +		} else {
   1.239 +			MessagingException e = new MessagingException("Zpráva nebo nastavení jsou nevyhovující");
   1.240 +			log.log(Level.SEVERE, null, e);
   1.241 +			throw e;
   1.242 +		}
   1.243  
   1.244 -    private static boolean zkontrolujAdresu(InternetAddressKomu a) {
   1.245 -        if (a.getTyp() == null) {
   1.246 -            log.log(Level.WARNING, "Neplatná adresa (typ): {0}", a.getAddress());
   1.247 -            return false;
   1.248 -        }
   1.249 +	}
   1.250  
   1.251 -        if (a.getAddress() == null || a.getAddress().length() < 1) {
   1.252 -            log.log(Level.WARNING, "Neplatná adresa (address): {0}", a.getPersonal());
   1.253 -            return false;
   1.254 -        }
   1.255 +	private String xhtmlNaProstýText(String xhtml) throws MessagingException {
   1.256 +		try {
   1.257 +			Transformer textTransformer = transformerFactory.newTransformer(new StreamSource(getClass().getClassLoader().getResourceAsStream("cz/frantovo/postak/odchozíEmailText.xsl")));
   1.258  
   1.259 -        if (!zkontrolujAdresu(a.getAddress())) {
   1.260 -            log.log(Level.WARNING, "Adresa nevyhovuje regulárnímu výrazu: {0}", a.getAddress());
   1.261 -            return false;
   1.262 -        }
   1.263 +			StringReader input = new StringReader(xhtml);
   1.264 +			StringWriter output = new StringWriter(xhtml.length());
   1.265 +			textTransformer.transform(new StreamSource(input), new StreamResult(output));
   1.266  
   1.267 -        return true;
   1.268 -    }
   1.269 +			return output.toString();
   1.270 +		} catch (Exception e) {
   1.271 +			throw new MessagingException("Chyba při XSL transformaci zprávy na prostý text.", e);
   1.272 +		}
   1.273 +	}
   1.274  
   1.275 -    /** Zkontroluje e-mailovou adresu pomocí regulárního výrazu. */
   1.276 -    public static boolean zkontrolujAdresu(String adresa) {
   1.277 -        return adresa != null && Pattern.matches(REGULARNI_EMAIL, adresa);
   1.278 -    }
   1.279 +	/**
   1.280 +	 * Nastaví zprávě (MimeMessage) všechny příjemce, které najde ve zprávě a nastavení.
   1.281 +	 * Respektuje typy příjemců: komu, kopie, skrytá kopie.
   1.282 +	 */
   1.283 +	private static void naplnPrijemce(MimeMessage mimeZprava, HromadnaZprava zprava) throws MessagingException {
   1.284 +		/**
   1.285 +		 * Příjemci se budou definovat pouze ve zprávě.
   1.286 +		 * Z nastavení se brát nebudou (výchozí příjemci už ve zprávě budou).
   1.287 +		 */
   1.288 +		ArrayList<InternetAddressKomu> prijemci = zprava.getPrijemci();
   1.289 +		for (InternetAddressKomu komu : prijemci) {
   1.290 +			if (zkontrolujAdresu(komu)) {
   1.291 +				mimeZprava.addRecipient(komu.getTyp(), komu);
   1.292 +			}
   1.293 +		}
   1.294 +	}
   1.295  
   1.296 -    /** @return  Vrací true, pokud je zpráva v pořádku */
   1.297 -    private static boolean zkontrolujZpravu(HromadnaZprava z) {
   1.298 +	/** Vypíše do logu seznam příjemců */
   1.299 +	public static void vypisPrijemce(Collection<InternetAddressKomu> prijemci) {
   1.300 +		for (InternetAddressKomu p : prijemci) {
   1.301 +			log.log(Level.INFO, p.toString());
   1.302 +		}
   1.303 +	}
   1.304  
   1.305 -        if (z.getPrijemci() == null) {
   1.306 -            log.log(Level.WARNING, "getPrijemci() == null");
   1.307 -            return false;
   1.308 -        }
   1.309 +	/** Veřejná odesílací metoda.
   1.310 +	 * Postará se o rozdělení zpráv na dílčí (které se vejdou do limitu příjemců)
   1.311 +	 */
   1.312 +	public void odesli(HromadnaZprava zprava) throws MessagingException {
   1.313 +		Collection<HromadnaZprava> zpravy = zprava.getDilciZpravy(nastaveni.getLimitZprav());
   1.314  
   1.315 -        if (z.getPrijemci().size() < 1) {
   1.316 -            log.log(Level.WARNING, "getPrijemci().size() < 1");
   1.317 -            return false;
   1.318 -        }
   1.319 +		for (HromadnaZprava dilciZprava : zpravy) {
   1.320 +			odesliSMTP(dilciZprava);
   1.321 +		}
   1.322 +	}
   1.323  
   1.324 -        if (z.getOdesilatel() == null) {
   1.325 -            log.log(Level.WARNING, "getOdesilatel() == null");
   1.326 -            return false;
   1.327 -        }
   1.328 +	private static boolean zkontrolujAdresu(InternetAddressKomu a) {
   1.329 +		if (a.getTyp() == null) {
   1.330 +			log.log(Level.WARNING, "Neplatná adresa (typ): {0}", a.getAddress());
   1.331 +			return false;
   1.332 +		}
   1.333  
   1.334 -        if (z.getPredmet() == null) {
   1.335 -            log.log(Level.WARNING, "getPredmet() == null");
   1.336 -            return false;
   1.337 -        }
   1.338 +		if (a.getAddress() == null || a.getAddress().length() < 1) {
   1.339 +			log.log(Level.WARNING, "Neplatná adresa (address): {0}", a.getPersonal());
   1.340 +			return false;
   1.341 +		}
   1.342  
   1.343 -        return true;
   1.344 -    }
   1.345 +		if (!zkontrolujAdresu(a.getAddress())) {
   1.346 +			log.log(Level.WARNING, "Adresa nevyhovuje regulárnímu výrazu: {0}", a.getAddress());
   1.347 +			return false;
   1.348 +		}
   1.349  
   1.350 -    private static boolean zkontrolujNastaveni(Nastaveni n) {
   1.351 +		return true;
   1.352 +	}
   1.353  
   1.354 -        if (n.getPostovniServer() == null || n.getPostovniServer().length() < 1) {
   1.355 -            return false;
   1.356 -        }
   1.357 +	/** Zkontroluje e-mailovou adresu pomocí regulárního výrazu. */
   1.358 +	public static boolean zkontrolujAdresu(String adresa) {
   1.359 +		return adresa != null && Pattern.matches(REGULARNI_EMAIL, adresa);
   1.360 +	}
   1.361  
   1.362 -        return true;
   1.363 -    }
   1.364 +	/** @return Vrací true, pokud je zpráva v pořádku */
   1.365 +	private static boolean zkontrolujZpravu(HromadnaZprava z) {
   1.366  
   1.367 -    /** Slouží k uložení jména a hesla pro SMTP */
   1.368 -    private static class PostakuvHeslovnik extends Authenticator {
   1.369 +		if (z.getPrijemci() == null) {
   1.370 +			log.log(Level.WARNING, "getPrijemci() == null");
   1.371 +			return false;
   1.372 +		}
   1.373  
   1.374 -        private String jmeno = "user";
   1.375 -        private char[] heslo = "luser".toCharArray();
   1.376 +		if (z.getPrijemci().size() < 1) {
   1.377 +			log.log(Level.WARNING, "getPrijemci().size() < 1");
   1.378 +			return false;
   1.379 +		}
   1.380  
   1.381 -        @Override
   1.382 -        public PasswordAuthentication getPasswordAuthentication() {
   1.383 -            return new PasswordAuthentication(jmeno, String.valueOf(heslo));
   1.384 -        }
   1.385 +		if (z.getOdesilatel() == null) {
   1.386 +			log.log(Level.WARNING, "getOdesilatel() == null");
   1.387 +			return false;
   1.388 +		}
   1.389  
   1.390 -        public void setJmenoHeslo(String jmeno, char[] heslo) {
   1.391 -            this.jmeno = jmeno;
   1.392 -            this.heslo = heslo;
   1.393 -        }
   1.394 -    }
   1.395 +		if (z.getPredmet() == null) {
   1.396 +			log.log(Level.WARNING, "getPredmet() == null");
   1.397 +			return false;
   1.398 +		}
   1.399 +
   1.400 +		return true;
   1.401 +	}
   1.402 +
   1.403 +	private static boolean zkontrolujNastaveni(Nastaveni n) {
   1.404 +
   1.405 +		if (n.getPostovniServer() == null || n.getPostovniServer().length() < 1) {
   1.406 +			return false;
   1.407 +		}
   1.408 +
   1.409 +		return true;
   1.410 +	}
   1.411 +
   1.412 +	/** Slouží k uložení jména a hesla pro SMTP */
   1.413 +	private static class PostakuvHeslovnik extends Authenticator {
   1.414 +
   1.415 +		private String jmeno = "user";
   1.416 +		private char[] heslo = "luser".toCharArray();
   1.417 +
   1.418 +		@Override
   1.419 +		public PasswordAuthentication getPasswordAuthentication() {
   1.420 +			return new PasswordAuthentication(jmeno, String.valueOf(heslo));
   1.421 +		}
   1.422 +
   1.423 +		public void setJmenoHeslo(String jmeno, char[] heslo) {
   1.424 +			this.jmeno = jmeno;
   1.425 +			this.heslo = heslo;
   1.426 +		}
   1.427 +	}
   1.428  }