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