Skip to content

Package: StreamLoader

StreamLoader

Coverage

1: /*
2: * Copyright (c) 1997, 2023 Oracle and/or its affiliates. All rights reserved.
3: *
4: * This program and the accompanying materials are made available under the
5: * terms of the Eclipse Public License v. 2.0, which is available at
6: * http://www.eclipse.org/legal/epl-2.0.
7: *
8: * This Source Code may also be made available under the following Secondary
9: * Licenses when the conditions for such availability set forth in the
10: * Eclipse Public License v. 2.0 are satisfied: GNU General Public License,
11: * version 2 with the GNU Classpath Exception, which is available at
12: * https://www.gnu.org/software/classpath/license.html.
13: *
14: * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
15: */
16:
17: package jakarta.mail;
18:
19: import jakarta.mail.util.LineInputStream;
20: import jakarta.mail.util.StreamProvider;
21:
22: import java.io.BufferedInputStream;
23: import java.io.File;
24: import java.io.FileInputStream;
25: import java.io.FileNotFoundException;
26: import java.io.IOException;
27: import java.io.InputStream;
28: import java.io.PrintStream;
29: import java.lang.annotation.Annotation;
30: import java.lang.reflect.Constructor;
31: import java.lang.reflect.Method;
32: import java.net.InetAddress;
33: import java.net.URL;
34: import java.security.AccessController;
35: import java.security.PrivilegedAction;
36: import java.security.PrivilegedActionException;
37: import java.security.PrivilegedExceptionAction;
38: import java.util.ArrayList;
39: import java.util.Collections;
40: import java.util.HashMap;
41: import java.util.Hashtable;
42: import java.util.Iterator;
43: import java.util.List;
44: import java.util.Map;
45: import java.util.Properties;
46: import java.util.ServiceLoader;
47: import java.util.StringTokenizer;
48: import java.util.concurrent.Executor;
49: import java.util.logging.Level;
50:
51: /**
52: * The Session class represents a mail session and is not subclassed.
53: * It collects together properties and defaults used by the mail API's.
54: * A single default session can be shared by multiple applications on the
55: * desktop. Unshared sessions can also be created. <p>
56: *
57: * The Session class provides access to the protocol providers that
58: * implement the <code>Store</code>, <code>Transport</code>, and related
59: * classes. The protocol providers are configured using the following files:
60: * <ul>
61: * <li> <code>javamail.providers</code> and
62: *         <code>javamail.default.providers</code> </li>
63: * <li> <code>javamail.address.map</code> and
64: *         <code>javamail.default.address.map</code> </li>
65: * </ul>
66: * <p>
67: * Each <code>javamail.</code><i>X</i> resource file is searched for using
68: * three methods in the following order:
69: * <ol>
70: * <li> <code><i>java.home</i>/<i>conf</i>/javamail.</code><i>X</i> </li>
71: * <li> <code>META-INF/javamail.</code><i>X</i> </li>
72: * <li> <code>META-INF/javamail.default.</code><i>X</i> </li>
73: * </ol>
74: * <p>
75: * (Where <i>java.home</i> is the value of the "java.home" System property
76: * and <i>conf</i> is the directory named "conf" if it exists,
77: * otherwise the directory named "lib"; the "conf" directory was
78: * introduced in JDK 1.9.)
79: * <p>
80: * The first method allows the user to include their own version of the
81: * resource file by placing it in the <i>conf</i> directory where the
82: * <code>java.home</code> property points. The second method allows an
83: * application that uses the Jakarta Mail APIs to include their own resource
84: * files in their application's or jar file's <code>META-INF</code>
85: * directory. The <code>javamail.default.</code><i>X</i> default files
86: * are part of the Jakarta Mail <code>mail.jar</code> file and should not be
87: * supplied by users. <p>
88: *
89: * File location depends upon how the <code>ClassLoader</code> method
90: * <code>getResource</code> is implemented. Usually, the
91: * <code>getResource</code> method searches through CLASSPATH until it
92: * finds the requested file and then stops. <p>
93: *
94: * The ordering of entries in the resource files matters. If multiple
95: * entries exist, the first entries take precedence over the later
96: * entries. For example, the first IMAP provider found will be set as the
97: * default IMAP implementation until explicitly changed by the
98: * application. The user- or system-supplied resource files augment, they
99: * do not override, the default files included with the Jakarta Mail APIs.
100: * This means that all entries in all files loaded will be available. <p>
101: *
102: * <b><code>javamail.providers</code></b> and
103: * <b><code>javamail.default.providers</code></b><p>
104: *
105: * These resource files specify the stores and transports that are
106: * available on the system, allowing an application to "discover" what
107: * store and transport implementations are available. The protocol
108: * implementations are listed one per line. The file format defines four
109: * attributes that describe a protocol implementation. Each attribute is
110: * an "="-separated name-value pair with the name in lowercase. Each
111: * name-value pair is semi-colon (";") separated. The following names
112: * are defined.
113: *
114: * <table border=1>
115: * <caption>
116: * Attribute Names in Providers Files
117: * </caption>
118: * <tr>
119: * <th>Name</th><th>Description</th>
120: * </tr>
121: * <tr>
122: * <td>protocol</td>
123: * <td>Name assigned to protocol.
124: * For example, <code>smtp</code> for Transport.</td>
125: * </tr>
126: * <tr>
127: * <td>type</td>
128: * <td>Valid entries are <code>store</code> and <code>transport</code>.</td>
129: * </tr>
130: * <tr>
131: * <td>class</td>
132: * <td>Class name that implements this protocol.</td>
133: * </tr>
134: * <tr>
135: * <td>vendor</td>
136: * <td>Optional string identifying the vendor.</td>
137: * </tr>
138: * <tr>
139: * <td>version</td>
140: * <td>Optional string identifying the version.</td>
141: * </tr>
142: * </table><p>
143: *
144: * Here's an example of <code>META-INF/javamail.default.providers</code>
145: * file contents:
146: * <pre>
147: * protocol=imap; type=store; class=com.sun.mail.imap.IMAPStore; vendor=Oracle;
148: * protocol=smtp; type=transport; class=com.sun.mail.smtp.SMTPTransport; vendor=Oracle;
149: * </pre><p>
150: *
151: * The current implementation also supports configuring providers using
152: * the Java SE {@link java.util.ServiceLoader ServiceLoader} mechanism.
153: * When creating your own provider, create a {@link Provider} subclass,
154: * for example:
155: * <pre>
156: * package com.example;
157: *
158: * import jakarta.mail.Provider;
159: *
160: * public class MyProvider extends Provider {
161: * public MyProvider() {
162: * super(Provider.Type.STORE, "myprot", MyStore.class.getName(),
163: * "Example", null);
164: * }
165: * }
166: * </pre>
167: * Then include a file named <code>META-INF/services/jakarta.mail.Provider</code>
168: * in your jar file that lists the name of your Provider class:
169: * <pre>
170: * com.example.MyProvider
171: * </pre>
172: * <p>
173: *
174: * <b><code>javamail.address.map</code></b> and
175: * <b><code>javamail.default.address.map</code></b><p>
176: *
177: * These resource files map transport address types to the transport
178: * protocol. The <code>getType</code> method of
179: * <code>jakarta.mail.Address</code> returns the address type. The
180: * <code>javamail.address.map</code> file maps the transport type to the
181: * protocol. The file format is a series of name-value pairs. Each key
182: * name should correspond to an address type that is currently installed
183: * on the system; there should also be an entry for each
184: * <code>jakarta.mail.Address</code> implementation that is present if it is
185: * to be used. For example, the
186: * <code>jakarta.mail.internet.InternetAddress</code> method
187: * <code>getType</code> returns "rfc822". Each referenced protocol should
188: * be installed on the system. For the case of <code>news</code>, below,
189: * the client should install a Transport provider supporting the nntp
190: * protocol. <p>
191: *
192: * Here are the typical contents of a <code>javamail.address.map</code> file:
193: * <pre>
194: * rfc822=smtp
195: * news=nntp
196: * </pre>
197: *
198: * @author John Mani
199: * @author Bill Shannon
200: * @author Max Spivak
201: */
202:
203: public final class Session {
204:
205: // Support legacy @DefaultProvider
206: private static final String DEFAULT_PROVIDER = "com.sun.mail.util.DefaultProvider";
207:
208: private final StreamProvider streamProvider;
209: private final Properties props;
210: private final Authenticator authenticator;
211: private final Hashtable<URLName, PasswordAuthentication> authTable
212: = new Hashtable<>();
213: private boolean debug = false;
214: private PrintStream out; // debug output stream
215: private MailLogger logger;
216: private final List<Provider> providers = new ArrayList<>();
217: private final Map<String, Provider> providersByProtocol = new HashMap<>();
218: private final Map<String, Provider> providersByClassName = new HashMap<>();
219: private final Properties addressMap = new Properties();
220: // maps type to protocol
221: // the queue of events to be delivered, if mail.event.scope===session
222: private final EventQueue q;
223:
224: // The default session.
225: private static Session defaultSession = null;
226:
227: private static final String confDir;
228:
229: static {
230: String dir = null;
231: try {
232: dir = AccessController.doPrivileged(
233: new PrivilegedAction<String>() {
234: @Override
235: public String run() {
236: String home = System.getProperty("java.home");
237: String newdir = home + File.separator + "conf";
238: File conf = new File(newdir);
239: if (conf.exists())
240: return newdir + File.separator;
241: else
242: return home + File.separator +
243: "lib" + File.separator;
244: }
245: });
246: } catch (Exception ex) {
247: // ignore any exceptions
248: }
249: confDir = dir;
250: }
251:
252: // Constructor is not public
253: private Session(Properties props, Authenticator authenticator) {
254: this.props = props;
255: this.authenticator = authenticator;
256: this.streamProvider = StreamProvider.provider();
257:
258: if (Boolean.valueOf(props.getProperty("mail.debug")).booleanValue())
259: debug = true;
260:
261: initLogger();
262: logger.log(Level.CONFIG, "Jakarta Mail version {0}", Version.version);
263:
264: // get the Class associated with the Authenticator
265: Class<?> cl;
266: if (authenticator != null) {
267: cl = authenticator.getClass();
268: } else {
269: // Use implementation class, because that class loader has access to jakarta.mail module and implementation resources
270: cl = streamProvider.getClass();
271: }
272: // load the resources
273: loadProviders(cl);
274: loadAddressMap(cl);
275: q = new EventQueue((Executor) props.get("mail.event.executor"));
276: }
277:
278: /**
279: * Get the stream provider instance of the session.
280: *
281: * @return the stream provider
282: * @since JavaMail 2.1
283: */
284: public StreamProvider getStreamProvider() {
285: return streamProvider;
286: }
287:
288: private final synchronized void initLogger() {
289: logger = new MailLogger(this.getClass(), "DEBUG", debug, getDebugOut());
290: }
291:
292: /**
293: * Get a new Session object.
294: *
295: * @param props Properties object that hold relevant properties.<br>
296: * It is expected that the client supplies values
297: * for the properties listed in Appendix A of the
298: * Jakarta Mail spec (particularly mail.store.protocol,
299: * mail.transport.protocol, mail.host, mail.user,
300: * and mail.from) as the defaults are unlikely to
301: * work in all cases.
302: * @param authenticator Authenticator object used to call back to
303: * the application when a user name and password is
304: * needed.
305: * @return a new Session object
306: * @see jakarta.mail.Authenticator
307: */
308: public static Session getInstance(Properties props, Authenticator authenticator) {
309: return new Session(props, authenticator);
310: }
311:
312: /**
313: * Get a new Session object.
314: *
315: * @param props Properties object that hold relevant properties.<br>
316: * It is expected that the client supplies values
317: * for the properties listed in Appendix A of the
318: * Jakarta Mail spec (particularly mail.store.protocol,
319: * mail.transport.protocol, mail.host, mail.user,
320: * and mail.from) as the defaults are unlikely to
321: * work in all cases.
322: * @return a new Session object
323: * @since JavaMail 1.2
324: */
325: public static Session getInstance(Properties props) {
326: return new Session(props, null);
327: }
328:
329: /**
330: * Get the default Session object. If a default has not yet been
331: * setup, a new Session object is created and installed as the
332: * default. <p>
333: *
334: * Since the default session is potentially available to all
335: * code executing in the same Java virtual machine, and the session
336: * can contain security sensitive information such as user names
337: * and passwords, access to the default session is restricted.
338: * The Authenticator object, which must be created by the caller,
339: * is used indirectly to check access permission. The Authenticator
340: * object passed in when the session is created is compared with
341: * the Authenticator object passed in to subsequent requests to
342: * get the default session. If both objects are the same, or are
343: * from the same ClassLoader, the request is allowed. Otherwise,
344: * it is denied. <p>
345: *
346: * Note that if the Authenticator object used to create the session
347: * is null, anyone can get the default session by passing in null. <p>
348: *
349: * Note also that the Properties object is used only the first time
350: * this method is called, when a new Session object is created.
351: * Subsequent calls return the Session object that was created by the
352: * first call, and ignore the passed Properties object. Use the
353: * <code>getInstance</code> method to get a new Session object every
354: * time the method is called. <p>
355: *
356: * Additional security Permission objects may be used to
357: * control access to the default session. <p>
358: *
359: * In the current implementation, if a SecurityManager is set, the
360: * caller must have the <code>RuntimePermission("setFactory")</code>
361: * permission.
362: *
363: * @param props Properties object. Used only if a new Session
364: * object is created.<br>
365: * It is expected that the client supplies values
366: * for the properties listed in Appendix A of the
367: * Jakarta Mail spec (particularly mail.store.protocol,
368: * mail.transport.protocol, mail.host, mail.user,
369: * and mail.from) as the defaults are unlikely to
370: * work in all cases.
371: * @param authenticator Authenticator object. Used only if a
372: * new Session object is created. Otherwise,
373: * it must match the Authenticator used to create
374: * the Session.
375: * @return the default Session object
376: */
377: public static synchronized Session getDefaultInstance(Properties props, Authenticator authenticator) {
378: if (defaultSession == null) {
379: SecurityManager security = System.getSecurityManager();
380: if (security != null)
381: security.checkSetFactory();
382: defaultSession = new Session(props, authenticator);
383: } else {
384: // have to check whether caller is allowed to see default session
385: if (defaultSession.authenticator == authenticator)
386: ; // either same object or both null, either way OK
387: else if (defaultSession.authenticator != null &&
388: authenticator != null &&
389: defaultSession.authenticator.getClass().getClassLoader() ==
390: authenticator.getClass().getClassLoader())
391: ; // both objects came from the same class loader, OK
392: else
393: // anything else is not allowed
394: throw new SecurityException("Access to default session denied");
395: }
396:
397: return defaultSession;
398: }
399:
400: /**
401: * Get the default Session object. If a default has not yet been
402: * setup, a new Session object is created and installed as the
403: * default. <p>
404: *
405: * Note that a default session created with no Authenticator is
406: * available to all code executing in the same Java virtual
407: * machine, and the session can contain security sensitive
408: * information such as user names and passwords.
409: *
410: * @param props Properties object. Used only if a new Session
411: * object is created.<br>
412: * It is expected that the client supplies values
413: * for the properties listed in Appendix A of the
414: * Jakarta Mail spec (particularly mail.store.protocol,
415: * mail.transport.protocol, mail.host, mail.user,
416: * and mail.from) as the defaults are unlikely to
417: * work in all cases.
418: * @return the default Session object
419: * @since JavaMail 1.2
420: */
421: public static Session getDefaultInstance(Properties props) {
422: return getDefaultInstance(props, null);
423: }
424:
425: /**
426: * Set the debug setting for this Session.
427: * <p>
428: * Since the debug setting can be turned on only after the Session
429: * has been created, to turn on debugging in the Session
430: * constructor, set the property <code>mail.debug</code> in the
431: * Properties object passed in to the constructor to true. The
432: * value of the <code>mail.debug</code> property is used to
433: * initialize the per-Session debugging flag. Subsequent calls to
434: * the <code>setDebug</code> method manipulate the per-Session
435: * debugging flag and have no effect on the <code>mail.debug</code>
436: * property.
437: *
438: * @param debug Debug setting
439: */
440: public synchronized void setDebug(boolean debug) {
441: this.debug = debug;
442: initLogger();
443: logger.log(Level.CONFIG, "setDebug: Jakarta Mail version {0}", Version.version);
444: }
445:
446: /**
447: * Get the debug setting for this Session.
448: *
449: * @return current debug setting
450: */
451: public synchronized boolean getDebug() {
452: return debug;
453: }
454:
455: /**
456: * Set the stream to be used for debugging output for this session.
457: * If <code>out</code> is null, <code>System.out</code> will be used.
458: * Note that debugging output that occurs before any session is created,
459: * as a result of setting the <code>mail.debug</code> system property,
460: * will always be sent to <code>System.out</code>.
461: *
462: * @param out the PrintStream to use for debugging output
463: * @since JavaMail 1.3
464: */
465: public synchronized void setDebugOut(PrintStream out) {
466: this.out = out;
467: initLogger();
468: }
469:
470: /**
471: * Returns the stream to be used for debugging output. If no stream
472: * has been set, <code>System.out</code> is returned.
473: *
474: * @return the PrintStream to use for debugging output
475: * @since JavaMail 1.3
476: */
477: public synchronized PrintStream getDebugOut() {
478: if (out == null)
479: return System.out;
480: else
481: return out;
482: }
483:
484: /**
485: * This method returns an array of all the implementations installed
486: * via the javamail.[default.]providers files that can
487: * be loaded using the ClassLoader available to this application.
488: *
489: * @return Array of configured providers
490: */
491: public synchronized Provider[] getProviders() {
492: Provider[] _providers = new Provider[providers.size()];
493: providers.toArray(_providers);
494: return _providers;
495: }
496:
497: /**
498: * Returns the default Provider for the protocol
499: * specified. Checks mail.<protocol>.class property
500: * first and if it exists, returns the Provider
501: * associated with this implementation. If it doesn't exist,
502: * returns the Provider that appeared first in the
503: * configuration files. If an implementation for the protocol
504: * isn't found, throws NoSuchProviderException
505: *
506: * @param protocol Configured protocol (i.e. smtp, imap, etc)
507: * @return Currently configured Provider for the specified protocol
508: * @throws NoSuchProviderException If a provider for the given
509: * protocol is not found.
510: */
511: public synchronized Provider getProvider(String protocol) throws NoSuchProviderException {
512:
513: if (protocol == null || protocol.length() <= 0) {
514: throw new NoSuchProviderException("Invalid protocol: null");
515: }
516:
517: Provider _provider = null;
518:
519: // check if the mail.<protocol>.class property exists
520: String _className = props.getProperty("mail." + protocol + ".class");
521: if (_className != null) {
522: if (logger.isLoggable(Level.FINE)) {
523: logger.fine("mail." + protocol +
524: ".class property exists and points to " +
525: _className);
526: }
527: _provider = providersByClassName.get(_className);
528: }
529:
530: if (_provider != null) {
531: return _provider;
532: } else {
533: // returning currently default protocol in providersByProtocol
534: _provider = providersByProtocol.get(protocol);
535: }
536:
537: if (_provider == null) {
538: throw new NoSuchProviderException("No provider for " + protocol);
539: } else {
540: if (logger.isLoggable(Level.FINE)) {
541: logger.fine("getProvider() returning " + _provider.toString());
542: }
543: return _provider;
544: }
545: }
546:
547: /**
548: * Set the passed Provider to be the default implementation
549: * for the protocol in Provider.protocol overriding any previous values.
550: *
551: * @param provider Currently configured Provider which will be
552: * set as the default for the protocol
553: * @throws NoSuchProviderException If the provider passed in
554: * is invalid.
555: */
556: public synchronized void setProvider(Provider provider)
557: throws NoSuchProviderException {
558: if (provider == null) {
559: throw new NoSuchProviderException("Can't set null provider");
560: }
561: providersByProtocol.put(provider.getProtocol(), provider);
562: providersByClassName.put(provider.getClassName(), provider);
563: props.put("mail." + provider.getProtocol() + ".class", provider.getClassName());
564: }
565:
566:
567: /**
568: * Get a Store object that implements this user's desired Store
569: * protocol. The <code>mail.store.protocol</code> property specifies the
570: * desired protocol. If an appropriate Store object is not obtained,
571: * NoSuchProviderException is thrown
572: *
573: * @return a Store object
574: * @throws NoSuchProviderException If a provider for the given
575: * protocol is not found.
576: */
577: public Store getStore() throws NoSuchProviderException {
578: return getStore(getProperty("mail.store.protocol"));
579: }
580:
581: /**
582: * Get a Store object that implements the specified protocol. If an
583: * appropriate Store object cannot be obtained,
584: * NoSuchProviderException is thrown.
585: *
586: * @param protocol the Store protocol
587: * @return a Store object
588: * @throws NoSuchProviderException If a provider for the given
589: * protocol is not found.
590: */
591: public Store getStore(String protocol) throws NoSuchProviderException {
592: return getStore(new URLName(protocol, null, -1, null, null, null));
593: }
594:
595:
596: /**
597: * Get a Store object for the given URLName. If the requested Store
598: * object cannot be obtained, NoSuchProviderException is thrown.
599: *
600: * The "scheme" part of the URL string (Refer RFC 1738) is used
601: * to locate the Store protocol.
602: *
603: * @param url URLName that represents the desired Store
604: * @return a closed Store object
605: * @throws NoSuchProviderException If a provider for the given
606: * URLName is not found.
607: * @see #getFolder(URLName)
608: * @see jakarta.mail.URLName
609: */
610: public Store getStore(URLName url) throws NoSuchProviderException {
611: String protocol = url.getProtocol();
612: Provider p = getProvider(protocol);
613: return getStore(p, url);
614: }
615:
616: /**
617: * Get an instance of the store specified by Provider. Instantiates
618: * the store and returns it.
619: *
620: * @param provider Store Provider that will be instantiated
621: * @return Instantiated Store
622: * @throws NoSuchProviderException If a provider for the given
623: * Provider is not found.
624: */
625: public Store getStore(Provider provider) throws NoSuchProviderException {
626: return getStore(provider, null);
627: }
628:
629:
630: /**
631: * Get an instance of the store specified by Provider. If the URLName
632: * is not null, uses it, otherwise creates a new one. Instantiates
633: * the store and returns it. This is a private method used by
634: * getStore(Provider) and getStore(URLName)
635: *
636: * @param provider Store Provider that will be instantiated
637: * @param url URLName used to instantiate the Store
638: * @return Instantiated Store
639: * @throws NoSuchProviderException If a provider for the given
640: * Provider/URLName is not found.
641: */
642: private Store getStore(Provider provider, URLName url) throws NoSuchProviderException {
643:
644: // make sure we have the correct type of provider
645: if (provider == null || provider.getType() != Provider.Type.STORE) {
646: throw new NoSuchProviderException("invalid provider");
647: }
648:
649: return getService(provider, url, Store.class);
650: }
651:
652: /**
653: * Get a closed Folder object for the given URLName. If the requested
654: * Folder object cannot be obtained, null is returned. <p>
655: *
656: * The "scheme" part of the URL string (Refer RFC 1738) is used
657: * to locate the Store protocol. The rest of the URL string (that is,
658: * the "schemepart", as per RFC 1738) is used by that Store
659: * in a protocol dependent manner to locate and instantiate the
660: * appropriate Folder object. <p>
661: *
662: * Note that RFC 1738 also specifies the syntax for the
663: * "schemepart" for IP-based protocols (IMAP4, POP3, etc.).
664: * Providers of IP-based mail Stores should implement that
665: * syntax for referring to Folders.
666: *
667: * @param url URLName that represents the desired folder
668: * @return Folder
669: * @throws NoSuchProviderException If a provider for the given
670: * URLName is not found.
671: * @throws MessagingException if the Folder could not be
672: * located or created.
673: * @see #getStore(URLName)
674: * @see jakarta.mail.URLName
675: */
676: public Folder getFolder(URLName url) throws MessagingException {
677: // First get the Store
678: Store store = getStore(url);
679: store.connect();
680: return store.getFolder(url);
681: }
682:
683: /**
684: * Get a Transport object that implements this user's desired
685: * Transport protcol. The <code>mail.transport.protocol</code> property
686: * specifies the desired protocol. If an appropriate Transport
687: * object cannot be obtained, MessagingException is thrown.
688: *
689: * @return a Transport object
690: * @throws NoSuchProviderException If the provider is not found.
691: */
692: public Transport getTransport() throws NoSuchProviderException {
693: String prot = getProperty("mail.transport.protocol");
694: if (prot != null)
695: return getTransport(prot);
696: // if the property isn't set, use the protocol for "rfc822"
697: prot = (String) addressMap.get("rfc822");
698: if (prot != null)
699: return getTransport(prot);
700: return getTransport("smtp"); // if all else fails
701: }
702:
703: /**
704: * Get a Transport object that implements the specified protocol.
705: * If an appropriate Transport object cannot be obtained, null is
706: * returned.
707: *
708: * @param protocol the Transport protocol
709: * @return a Transport object
710: * @throws NoSuchProviderException If provider for the given
711: * protocol is not found.
712: */
713: public Transport getTransport(String protocol)
714: throws NoSuchProviderException {
715: return getTransport(new URLName(protocol, null, -1, null, null, null));
716: }
717:
718:
719: /**
720: * Get a Transport object for the given URLName. If the requested
721: * Transport object cannot be obtained, NoSuchProviderException is thrown.
722: *
723: * The "scheme" part of the URL string (Refer RFC 1738) is used
724: * to locate the Transport protocol.
725: *
726: * @param url URLName that represents the desired Transport
727: * @return a closed Transport object
728: * @throws NoSuchProviderException If a provider for the given
729: * URLName is not found.
730: * @see jakarta.mail.URLName
731: */
732: public Transport getTransport(URLName url) throws NoSuchProviderException {
733: String protocol = url.getProtocol();
734: Provider p = getProvider(protocol);
735: return getTransport(p, url);
736: }
737:
738: /**
739: * Get an instance of the transport specified in the Provider. Instantiates
740: * the transport and returns it.
741: *
742: * @param provider Transport Provider that will be instantiated
743: * @return Instantiated Transport
744: * @throws NoSuchProviderException If provider for the given
745: * provider is not found.
746: */
747: public Transport getTransport(Provider provider) throws NoSuchProviderException {
748: return getTransport(provider, null);
749: }
750:
751: /**
752: * Get a Transport object that can transport a Message of the
753: * specified address type.
754: *
755: * @param address an address for which a Transport is needed
756: * @return A Transport object
757: * @throws NoSuchProviderException If provider for the
758: * Address type is not found
759: * @see jakarta.mail.Address
760: */
761: public Transport getTransport(Address address) throws NoSuchProviderException {
762:
763: String transportProtocol;
764: transportProtocol =
765: getProperty("mail.transport.protocol." + address.getType());
766: if (transportProtocol != null)
767: return getTransport(transportProtocol);
768: transportProtocol = (String) addressMap.get(address.getType());
769: if (transportProtocol != null)
770: return getTransport(transportProtocol);
771: throw new NoSuchProviderException("No provider for Address type: " + address.getType());
772: }
773:
774: /**
775: * Get a Transport object using the given provider and urlname.
776: *
777: * @param provider the provider to use
778: * @param url urlname to use (can be null)
779: * @return A Transport object
780: * @throws NoSuchProviderException If no provider or the provider
781: * was the wrong class.
782: */
783:
784: private Transport getTransport(Provider provider, URLName url) throws NoSuchProviderException {
785: // make sure we have the correct type of provider
786: if (provider == null || provider.getType() != Provider.Type.TRANSPORT) {
787: throw new NoSuchProviderException("invalid provider");
788: }
789:
790: return getService(provider, url, Transport.class);
791: }
792:
793: /**
794: * Get a Service object. Needs a provider object, but will
795: * create a URLName if needed. It attempts to instantiate
796: * the correct class.
797: *
798: * @param provider which provider to use
799: * @param url which URLName to use (can be null)
800: * @param type the service type (class)
801: * @throws NoSuchProviderException thrown when the class cannot be
802: * found or when it does not have the correct constructor
803: * (Session, URLName), or if it is not derived from
804: * Service.
805: */
806: private <T extends Service> T getService(Provider provider, URLName url, Class<T> type) throws NoSuchProviderException {
807: // need a provider and url
808: if (provider == null) {
809: throw new NoSuchProviderException("null");
810: }
811:
812: // create a url if needed
813: if (url == null) {
814: url = new URLName(provider.getProtocol(), null, -1,
815: null, null, null);
816: }
817:
818: Object service = null;
819:
820: // get the ClassLoader associated with the Authenticator
821: ClassLoader cl;
822: if (authenticator != null)
823: cl = authenticator.getClass().getClassLoader();
824: else
825: cl = this.getClass().getClassLoader();
826:
827: // now load the class
828: Class<?> serviceClass = null;
829: try {
830: // First try the "application's" class loader.
831: ClassLoader ccl = getContextClassLoader();
832: if (ccl != null)
833: try {
834: serviceClass =
835: Class.forName(provider.getClassName(), false, ccl);
836: } catch (ClassNotFoundException ex) {
837: // ignore it
838: }
839: if (serviceClass == null || !type.isAssignableFrom(serviceClass))
840: serviceClass =
841: Class.forName(provider.getClassName(), false, cl);
842:
843: if (!type.isAssignableFrom(serviceClass))
844: throw new ClassCastException(
845: type.getName() + " " + serviceClass.getName());
846: } catch (Exception ex1) {
847: // That didn't work, now try the "system" class loader.
848: // (Need both of these because JDK 1.1 class loaders
849: // may not delegate to their parent class loader.)
850: try {
851: serviceClass = Class.forName(provider.getClassName());
852: if (!type.isAssignableFrom(serviceClass))
853: throw new ClassCastException(
854: type.getName() + " " + serviceClass.getName());
855: } catch (Exception ex) {
856: // Nothing worked, give up.
857: logger.log(Level.FINE, "Exception loading provider", ex);
858: throw new NoSuchProviderException(provider.getProtocol());
859: }
860: }
861:
862: // construct an instance of the class
863: try {
864: Class<?>[] c = {jakarta.mail.Session.class, jakarta.mail.URLName.class};
865: Constructor<?> cons = serviceClass.getConstructor(c);
866:
867: Object[] o = {this, url};
868: service = cons.newInstance(o);
869:
870: } catch (Exception ex) {
871: logger.log(Level.FINE, "Exception loading provider", ex);
872: throw new NoSuchProviderException(provider.getProtocol());
873: }
874:
875: return type.cast(service);
876: }
877:
878: /**
879: * Save a PasswordAuthentication for this (store or transport) URLName.
880: * If pw is null the entry corresponding to the URLName is removed.
881: * <p>
882: * This is normally used only by the store or transport implementations
883: * to allow authentication information to be shared among multiple
884: * uses of a session.
885: *
886: * @param url the URLName
887: * @param pw the PasswordAuthentication to save
888: */
889: public void setPasswordAuthentication(URLName url, PasswordAuthentication pw) {
890: if (pw == null)
891: authTable.remove(url);
892: else
893: authTable.put(url, pw);
894: }
895:
896: /**
897: * Return any saved PasswordAuthentication for this (store or transport)
898: * URLName. Normally used only by store or transport implementations.
899: *
900: * @param url the URLName
901: * @return the PasswordAuthentication corresponding to the URLName
902: */
903: public PasswordAuthentication getPasswordAuthentication(URLName url) {
904: return authTable.get(url);
905: }
906:
907: /**
908: * Call back to the application to get the needed user name and password.
909: * The application should put up a dialog something like:
910: * <pre>
911: * Connecting to <protocol> mail service on host <addr>, port <port>.
912: * <prompt>
913: *
914: * User Name: <defaultUserName>
915: * Password:
916: * </pre>
917: *
918: * @param addr InetAddress of the host. may be null.
919: * @param port the port on the host
920: * @param protocol protocol scheme (e.g. imap, pop3, etc.)
921: * @param prompt any additional String to show as part of
922: * the prompt; may be null.
923: * @param defaultUserName the default username. may be null.
924: * @return the authentication which was collected by the authenticator;
925: * may be null.
926: */
927: public PasswordAuthentication requestPasswordAuthentication(InetAddress addr, int port, String protocol, String prompt, String defaultUserName) {
928: if (authenticator != null) {
929: return authenticator.requestPasswordAuthentication(
930: addr, port, protocol, prompt, defaultUserName);
931: } else {
932: return null;
933: }
934: }
935:
936: /**
937: * Returns the Properties object associated with this Session
938: *
939: * @return Properties object
940: */
941: public Properties getProperties() {
942: return props;
943: }
944:
945: /**
946: * Returns the value of the specified property. Returns null
947: * if this property does not exist.
948: *
949: * @param name the property name
950: * @return String that is the property value
951: */
952: public String getProperty(String name) {
953: return props.getProperty(name);
954: }
955:
956: static boolean containsDefaultProvider(Provider provider) {
957: Annotation[] annotations = provider.getClass().getDeclaredAnnotations();
958: for (Annotation annotation : annotations) {
959: if (DEFAULT_PROVIDER.equals(annotation.annotationType().getName())) {
960: return true;
961: }
962: }
963: return false;
964: }
965:
966: /**
967: * Load the protocol providers config files.
968: */
969: private void loadProviders(Class<?> cl) {
970: StreamLoader loader = new StreamLoader() {
971: @Override
972: public void load(InputStream is) throws IOException {
973: loadProvidersFromStream(is);
974: }
975: };
976:
977: // load system-wide javamail.providers from the
978: // <java.home>/{conf,lib} directory
979: try {
980: if (confDir != null)
981: loadFile(confDir + "javamail.providers", loader);
982: } catch (SecurityException ex) {
983: }
984:
985: // next, add all the non-default services
986: ServiceLoader<Provider> sl = ServiceLoader.load(Provider.class);
987: for (Provider p : sl) {
988: if (!containsDefaultProvider(p))
989: addProvider(p);
990: }
991:
992: // + handle Glassfish/OSGi (platform specific default)
993: if (isHk2Available()) {
994: Iterator<Provider> iter = lookupUsingHk2ServiceLoader(Provider.class.getName());
995: while (iter.hasNext()) {
996: Provider p = iter.next();
997: if (!containsDefaultProvider(p))
998: addProvider(p);
999: }
1000: }
1001:
1002: // load the META-INF/javamail.providers file supplied by an application
1003: loadAllResources("META-INF/javamail.providers", cl, loader);
1004:
1005: // load default META-INF/javamail.default.providers from mail.jar file
1006: loadResource("/META-INF/javamail.default.providers", cl, loader, false);
1007:
1008: // finally, add all the default services
1009: sl = ServiceLoader.load(Provider.class);
1010: for (Provider p : sl) {
1011: if (containsDefaultProvider(p))
1012: addProvider(p);
1013: }
1014:
1015: // + handle Glassfish/OSGi (platform specific default)
1016: if (isHk2Available()) {
1017: Iterator<Provider> iter = lookupUsingHk2ServiceLoader(Provider.class.getName());
1018: while (iter.hasNext()) {
1019: Provider p = iter.next();
1020: if (containsDefaultProvider(p)) {
1021: addProvider(p);
1022: }
1023: }
1024: }
1025:
1026: /*
1027: * If we haven't loaded any providers, fake it.
1028: */
1029: if (providers.size() == 0) {
1030: logger.config("failed to load any providers, using defaults");
1031: // failed to load any providers, initialize with our defaults
1032: addProvider(new Provider(Provider.Type.STORE,
1033: "imap", "com.sun.mail.imap.IMAPStore",
1034: "Oracle", Version.version));
1035: addProvider(new Provider(Provider.Type.STORE,
1036: "imaps", "com.sun.mail.imap.IMAPSSLStore",
1037: "Oracle", Version.version));
1038: addProvider(new Provider(Provider.Type.STORE,
1039: "pop3", "com.sun.mail.pop3.POP3Store",
1040: "Oracle", Version.version));
1041: addProvider(new Provider(Provider.Type.STORE,
1042: "pop3s", "com.sun.mail.pop3.POP3SSLStore",
1043: "Oracle", Version.version));
1044: addProvider(new Provider(Provider.Type.TRANSPORT,
1045: "smtp", "com.sun.mail.smtp.SMTPTransport",
1046: "Oracle", Version.version));
1047: addProvider(new Provider(Provider.Type.TRANSPORT,
1048: "smtps", "com.sun.mail.smtp.SMTPSSLTransport",
1049: "Oracle", Version.version));
1050: }
1051:
1052: if (logger.isLoggable(Level.CONFIG)) {
1053: // dump the output of the tables for debugging
1054: logger.config("Tables of loaded providers");
1055: logger.config("Providers Listed By Class Name: " +
1056: providersByClassName.toString());
1057: logger.config("Providers Listed By Protocol: " +
1058: providersByProtocol.toString());
1059: }
1060: }
1061:
1062: private void loadProvidersFromStream(InputStream is) throws IOException {
1063: if (is != null) {
1064: LineInputStream lis = streamProvider.inputLineStream(is, false);
1065: String currLine;
1066:
1067: // load and process one line at a time using LineInputStream
1068: while ((currLine = lis.readLine()) != null) {
1069:
1070: if (currLine.startsWith("#"))
1071: continue;
1072: if (currLine.trim().length() == 0)
1073: continue; // skip blank line
1074: Provider.Type type = null;
1075: String protocol = null, className = null;
1076: String vendor = null, version = null;
1077:
1078: // separate line into key-value tuples
1079: StringTokenizer tuples = new StringTokenizer(currLine, ";");
1080: while (tuples.hasMoreTokens()) {
1081: String currTuple = tuples.nextToken().trim();
1082:
1083: // set the value of each attribute based on its key
1084: int sep = currTuple.indexOf("=");
1085: if (currTuple.startsWith("protocol=")) {
1086: protocol = currTuple.substring(sep + 1);
1087: } else if (currTuple.startsWith("type=")) {
1088: String strType = currTuple.substring(sep + 1);
1089: if (strType.equalsIgnoreCase("store")) {
1090: type = Provider.Type.STORE;
1091: } else if (strType.equalsIgnoreCase("transport")) {
1092: type = Provider.Type.TRANSPORT;
1093: }
1094: } else if (currTuple.startsWith("class=")) {
1095: className = currTuple.substring(sep + 1);
1096: } else if (currTuple.startsWith("vendor=")) {
1097: vendor = currTuple.substring(sep + 1);
1098: } else if (currTuple.startsWith("version=")) {
1099: version = currTuple.substring(sep + 1);
1100: }
1101: }
1102:
1103: // check if a valid Provider; else, continue
1104: if (type == null || protocol == null || className == null
1105: || protocol.length() <= 0 || className.length() <= 0) {
1106:
1107: logger.log(Level.CONFIG, "Bad provider entry: {0}",
1108: currLine);
1109: continue;
1110: }
1111: Provider provider = new Provider(type, protocol, className,
1112: vendor, version);
1113:
1114: // add the newly-created Provider to the lookup tables
1115: addProvider(provider);
1116: }
1117: }
1118: }
1119:
1120: /**
1121: * Add a provider to the session.
1122: *
1123: * @param provider the provider to add
1124: * @since JavaMail 1.4
1125: */
1126: public synchronized void addProvider(Provider provider) {
1127: providers.add(provider);
1128: providersByClassName.put(provider.getClassName(), provider);
1129: if (!providersByProtocol.containsKey(provider.getProtocol()))
1130: providersByProtocol.put(provider.getProtocol(), provider);
1131: }
1132:
1133: // load maps in reverse order of preference so that the preferred
1134: // map is loaded last since its entries will override the previous ones
1135: private void loadAddressMap(Class<?> cl) {
1136: StreamLoader loader = new StreamLoader() {
1137: @Override
1138: public void load(InputStream is) throws IOException {
1139: addressMap.load(is);
1140: }
1141: };
1142:
1143: // load default META-INF/javamail.default.address.map from mail.jar
1144: loadResource("/META-INF/javamail.default.address.map", cl, loader, true);
1145:
1146: // load the META-INF/javamail.address.map file supplied by an app
1147: loadAllResources("META-INF/javamail.address.map", cl, loader);
1148:
1149: // load system-wide javamail.address.map from the
1150: // <java.home>/{conf,lib} directory
1151: try {
1152: if (confDir != null)
1153: loadFile(confDir + "javamail.address.map", loader);
1154: } catch (SecurityException ex) {
1155: }
1156:
1157: if (addressMap.isEmpty()) {
1158: logger.config("failed to load address map, using defaults");
1159: addressMap.put("rfc822", "smtp");
1160: }
1161: }
1162:
1163: /**
1164: * Set the default transport protocol to use for addresses of
1165: * the specified type. Normally the default is set by the
1166: * <code>javamail.default.address.map</code> or
1167: * <code>javamail.address.map</code> files or resources.
1168: *
1169: * @param addresstype type of address
1170: * @param protocol name of protocol
1171: * @see #getTransport(Address)
1172: * @since JavaMail 1.4
1173: */
1174: public synchronized void setProtocolForAddress(String addresstype, String protocol) {
1175: if (protocol == null)
1176: addressMap.remove(addresstype);
1177: else
1178: addressMap.put(addresstype, protocol);
1179: }
1180:
1181: /**
1182: * Load from the named file.
1183: */
1184: private void loadFile(String name, StreamLoader loader) {
1185: InputStream clis = null;
1186: try {
1187: clis = new BufferedInputStream(new FileInputStream(name));
1188: loader.load(clis);
1189: logger.log(Level.CONFIG, "successfully loaded file: {0}", name);
1190: } catch (FileNotFoundException fex) {
1191: // ignore it
1192: } catch (IOException e) {
1193: if (logger.isLoggable(Level.CONFIG))
1194: logger.log(Level.CONFIG, "not loading file: " + name, e);
1195: } catch (SecurityException sex) {
1196: if (logger.isLoggable(Level.CONFIG))
1197: logger.log(Level.CONFIG, "not loading file: " + name, sex);
1198: } finally {
1199: try {
1200: if (clis != null)
1201: clis.close();
1202: } catch (IOException ex) {
1203: } // ignore it
1204: }
1205: }
1206:
1207: /**
1208: * Load from the named resource.
1209: */
1210: private void loadResource(String name, Class<?> cl, StreamLoader loader, boolean expected) {
1211: InputStream clis = null;
1212: try {
1213: clis = getResourceAsStream(cl, name);
1214: if (clis != null) {
1215: loader.load(clis);
1216: logger.log(Level.CONFIG, "successfully loaded resource: {0}",
1217: name);
1218: } else {
1219: if (expected)
1220: logger.log(Level.WARNING,
1221: "expected resource not found: {0}", name);
1222: }
1223: } catch (IOException e) {
1224: logger.log(Level.CONFIG, "Exception loading resource", e);
1225: } catch (SecurityException sex) {
1226: logger.log(Level.CONFIG, "Exception loading resource", sex);
1227: } finally {
1228: try {
1229: if (clis != null)
1230: clis.close();
1231: } catch (IOException ex) {
1232: } // ignore it
1233: }
1234: }
1235:
1236: /**
1237: * Load all of the named resource.
1238: */
1239: private void loadAllResources(String name, Class<?> cl, StreamLoader loader) {
1240: boolean anyLoaded = false;
1241: try {
1242: URL[] urls;
1243: ClassLoader cld = null;
1244: // First try the "application's" class loader.
1245: cld = getContextClassLoader();
1246: if (cld == null)
1247: cld = cl.getClassLoader();
1248: if (cld != null)
1249: urls = getResources(cld, name);
1250: else
1251: urls = getSystemResources(name);
1252: if (urls != null) {
1253: for (int i = 0; i < urls.length; i++) {
1254: URL url = urls[i];
1255: InputStream clis = null;
1256: logger.log(Level.CONFIG, "URL {0}", url);
1257: try {
1258: clis = openStream(url);
1259: if (clis != null) {
1260: loader.load(clis);
1261: anyLoaded = true;
1262: logger.log(Level.CONFIG,
1263: "successfully loaded resource: {0}", url);
1264: } else {
1265: logger.log(Level.CONFIG,
1266: "not loading resource: {0}", url);
1267: }
1268: } catch (FileNotFoundException fex) {
1269: // ignore it
1270: } catch (IOException ioex) {
1271: logger.log(Level.CONFIG, "Exception loading resource",
1272: ioex);
1273: } catch (SecurityException sex) {
1274: logger.log(Level.CONFIG, "Exception loading resource",
1275: sex);
1276: } finally {
1277: try {
1278: if (clis != null)
1279: clis.close();
1280: } catch (IOException cex) {
1281: }
1282: }
1283: }
1284: }
1285: } catch (Exception ex) {
1286: logger.log(Level.CONFIG, "Exception loading resource", ex);
1287: }
1288:
1289: // if failed to load anything, fall back to old technique, just in case
1290: if (!anyLoaded) {
1291:                  /*
1292:                  logger.config("!anyLoaded");
1293:                  */
1294: loadResource("/" + name, cl, loader, false);
1295: }
1296: }
1297:
1298: /*
1299: * Following are security related methods that work on JDK 1.2 or newer.
1300: */
1301:
1302: static ClassLoader getContextClassLoader() {
1303: return AccessController.doPrivileged(
1304: new PrivilegedAction<ClassLoader>() {
1305: @Override
1306: public ClassLoader run() {
1307: ClassLoader cl = null;
1308: try {
1309: cl = Thread.currentThread().getContextClassLoader();
1310: } catch (SecurityException ex) {
1311: }
1312: return cl;
1313: }
1314: }
1315: );
1316: }
1317:
1318: private static InputStream getResourceAsStream(final Class<?> c, final String name) throws IOException {
1319: try {
1320: return AccessController.doPrivileged(
1321: new PrivilegedExceptionAction<InputStream>() {
1322: @Override
1323: public InputStream run() throws IOException {
1324: try {
1325: return c.getResourceAsStream(name);
1326: } catch (RuntimeException e) {
1327: // gracefully handle ClassLoader bugs (Tomcat)
1328: IOException ioex = new IOException(
1329: "ClassLoader.getResourceAsStream failed");
1330: ioex.initCause(e);
1331: throw ioex;
1332: }
1333: }
1334: }
1335: );
1336: } catch (PrivilegedActionException e) {
1337: throw (IOException) e.getException();
1338: }
1339: }
1340:
1341: private static URL[] getResources(final ClassLoader cl, final String name) {
1342: return AccessController.doPrivileged(new PrivilegedAction<URL[]>() {
1343: @Override
1344: public URL[] run() {
1345: URL[] ret = null;
1346: try {
1347: List<URL> v = Collections.list(cl.getResources(name));
1348: if (!v.isEmpty()) {
1349: ret = new URL[v.size()];
1350: v.toArray(ret);
1351: }
1352: } catch (IOException ioex) {
1353: } catch (SecurityException ex) {
1354: }
1355: return ret;
1356: }
1357: });
1358: }
1359:
1360: private static URL[] getSystemResources(final String name) {
1361: return AccessController.doPrivileged(new PrivilegedAction<URL[]>() {
1362: @Override
1363: public URL[] run() {
1364: URL[] ret = null;
1365: try {
1366: List<URL> v = Collections.list(
1367: ClassLoader.getSystemResources(name));
1368: if (!v.isEmpty()) {
1369: ret = new URL[v.size()];
1370: v.toArray(ret);
1371: }
1372: } catch (IOException ioex) {
1373: } catch (SecurityException ex) {
1374: }
1375: return ret;
1376: }
1377: });
1378: }
1379:
1380: private static InputStream openStream(final URL url) throws IOException {
1381: try {
1382: return AccessController.doPrivileged(
1383: new PrivilegedExceptionAction<InputStream>() {
1384: @Override
1385: public InputStream run() throws IOException {
1386: return url.openStream();
1387: }
1388: }
1389: );
1390: } catch (PrivilegedActionException e) {
1391: throw (IOException) e.getException();
1392: }
1393: }
1394:
1395: EventQueue getEventQueue() {
1396: return q;
1397: }
1398:
1399: private static final String OSGI_SERVICE_LOADER_CLASS_NAME = "org.glassfish.hk2.osgiresourcelocator.ServiceLoader";
1400:
1401: private static boolean isHk2Available() {
1402: try {
1403: Class.forName(OSGI_SERVICE_LOADER_CLASS_NAME);
1404: return true;
1405: } catch (ClassNotFoundException ignored) {
1406: }
1407: return false;
1408: }
1409:
1410: @SuppressWarnings({"unchecked"})
1411: private <T> Iterator<T> lookupUsingHk2ServiceLoader(String factoryId) {
1412: try {
1413: // Use reflection to avoid having any dependency on HK2 ServiceLoader class
1414: Class<?> serviceClass = Class.forName(factoryId);
1415: Class<?>[] args = new Class<?>[]{serviceClass};
1416: Class<?> target = Class.forName(OSGI_SERVICE_LOADER_CLASS_NAME);
1417: Method m = target.getMethod("lookupProviderInstances", Class.class);
1418: Iterable<T> result = ((Iterable<T>) m.invoke(null, (Object[]) args));
1419: return result != null ? result.iterator() : Collections.emptyIterator();
1420: } catch (Exception ignored) {
1421: // log and continue
1422: return Collections.emptyIterator();
1423: }
1424: }
1425:
1426: }
1427:
1428: /**
1429: * Support interface to generalize
1430: * code that loads resources from stream.
1431: */
1432: interface StreamLoader {
1433: void load(InputStream is) throws IOException;
1434: }