001    /*
002     * Licensed to the Apache Software Foundation (ASF) under one
003     * or more contributor license agreements.  See the NOTICE file
004     * distributed with this work for additional information
005     * regarding copyright ownership.  The ASF licenses this file
006     * to you under the Apache License, Version 2.0 (the
007     * "License"); you may not use this file except in compliance
008     * with the License.  You may obtain a copy of the License at
009     *
010     *  http://www.apache.org/licenses/LICENSE-2.0
011     *
012     * Unless required by applicable law or agreed to in writing,
013     * software distributed under the License is distributed on an
014     * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
015     * KIND, either express or implied.  See the License for the
016     * specific language governing permissions and limitations
017     * under the License.
018     */
019    
020    package javax.mail;
021    
022    import java.io.BufferedReader;
023    import java.io.File;
024    import java.io.FileInputStream;
025    import java.io.IOException;
026    import java.io.InputStream;
027    import java.io.InputStreamReader;
028    import java.io.PrintStream;
029    import java.lang.reflect.Constructor;
030    import java.lang.reflect.InvocationTargetException;
031    import java.net.InetAddress;
032    import java.net.URL;
033    import java.util.ArrayList;
034    import java.util.Enumeration;
035    import java.util.HashMap;
036    import java.util.List;
037    import java.util.Map;
038    import java.util.Properties;
039    import java.util.StringTokenizer;
040    import java.util.WeakHashMap;
041    
042    
043    /**
044     * OK, so we have a final class in the API with a heck of a lot of implementation required...
045     * let's try and figure out what it is meant to do.
046     * <p/>
047     * It is supposed to collect together properties and defaults so that they can be
048     * shared by multiple applications on a desktop; with process isolation and no
049     * real concept of shared memory, this seems challenging. These properties and
050     * defaults rely on system properties, making management in a app server harder,
051     * and on resources loaded from "mail.jar" which may lead to skew between                    
052     * differnet independent implementations of this API.
053     *
054     * @version $Rev: 729458 $ $Date: 2008-12-26 08:10:25 +0100 (Fr, 26. Dez 2008) $
055     */
056    public final class Session {
057        private static final Class[] PARAM_TYPES = {Session.class, URLName.class};
058        private static final WeakHashMap addressMapsByClassLoader = new WeakHashMap();
059        private static Session DEFAULT_SESSION;
060    
061        private Map passwordAuthentications = new HashMap();
062    
063        private final Properties properties;
064        private final Authenticator authenticator;
065        private boolean debug;
066        private PrintStream debugOut = System.out;
067    
068        private static final WeakHashMap providersByClassLoader = new WeakHashMap();
069    
070        /**
071         * No public constrcutor allowed.
072         */
073        private Session(Properties properties, Authenticator authenticator) {
074            this.properties = properties;
075            this.authenticator = authenticator;
076            debug = Boolean.valueOf(properties.getProperty("mail.debug")).booleanValue();
077        }
078    
079        /**
080         * Create a new session initialized with the supplied properties which uses the supplied authenticator.
081         * Clients should ensure the properties listed in Appendix A of the JavaMail specification are
082         * set as the defaults are unlikey to work in most scenarios; particular attention should be given
083         * to:
084         * <ul>
085         * <li>mail.store.protocol</li>
086         * <li>mail.transport.protocol</li>
087         * <li>mail.host</li>
088         * <li>mail.user</li>
089         * <li>mail.from</li>
090         * </ul>
091         *
092         * @param properties    the session properties
093         * @param authenticator an authenticator for callbacks to the user
094         * @return a new session
095         */
096        public static Session getInstance(Properties properties, Authenticator authenticator) {
097            return new Session(new Properties(properties), authenticator);
098        }
099    
100        /**
101         * Create a new session initialized with the supplied properties with no authenticator.
102         *
103         * @param properties the session properties
104         * @return a new session
105         * @see #getInstance(java.util.Properties, Authenticator)
106         */
107        public static Session getInstance(Properties properties) {
108            return getInstance(properties, null);
109        }
110    
111        /**
112         * Get the "default" instance assuming no authenticator is required.
113         *
114         * @param properties the session properties
115         * @return if "default" session
116         * @throws SecurityException if the does not have permission to access the default session
117         */
118        public synchronized static Session getDefaultInstance(Properties properties) {
119            return getDefaultInstance(properties, null);
120        }
121    
122        /**
123         * Get the "default" session.
124         * If there is not current "default", a new Session is created and installed as the default.
125         *
126         * @param properties
127         * @param authenticator
128         * @return if "default" session
129         * @throws SecurityException if the does not have permission to access the default session
130         */
131        public synchronized static Session getDefaultInstance(Properties properties, Authenticator authenticator) {
132            if (DEFAULT_SESSION == null) {
133                DEFAULT_SESSION = getInstance(properties, authenticator);
134            } else {
135                if (authenticator != DEFAULT_SESSION.authenticator) {
136                    if (authenticator == null || DEFAULT_SESSION.authenticator == null || authenticator.getClass().getClassLoader() != DEFAULT_SESSION.authenticator.getClass().getClassLoader()) {
137                        throw new SecurityException();
138                    }
139                }
140                // todo we should check with the SecurityManager here as well
141            }
142            return DEFAULT_SESSION;
143        }
144    
145        /**
146         * Enable debugging for this session.
147         * Debugging can also be enabled by setting the "mail.debug" property to true when
148         * the session is being created.
149         *
150         * @param debug the debug setting
151         */
152        public void setDebug(boolean debug) {
153            this.debug = debug;
154        }
155    
156        /**
157         * Get the debug setting for this session.
158         *
159         * @return the debug setting
160         */
161        public boolean getDebug() {
162            return debug;
163        }
164    
165        /**
166         * Set the output stream where debug information should be sent.
167         * If set to null, System.out will be used.
168         *
169         * @param out the stream to write debug information to
170         */
171        public void setDebugOut(PrintStream out) {
172            debugOut = out == null ? System.out : out;
173        }
174    
175        /**
176         * Return the debug output stream.
177         *
178         * @return the debug output stream
179         */
180        public PrintStream getDebugOut() {
181            return debugOut;
182        }
183    
184        /**
185         * Return the list of providers available to this application.
186         * This method searches for providers that are defined in the javamail.providers
187         * and javamail.default.providers resources available through the current context
188         * classloader, or if that is not available, the classloader that loaded this class.
189         * <p/>
190         * As searching for providers is potentially expensive, this implementation maintains
191         * a WeakHashMap of providers indexed by ClassLoader.
192         *
193         * @return an array of providers
194         */
195        public Provider[] getProviders() {
196            ProviderInfo info = getProviderInfo();
197            return (Provider[]) info.all.toArray(new Provider[info.all.size()]);
198        }
199    
200        /**
201         * Return the provider for a specific protocol.
202         * This implementation initially looks in the Session properties for an property with the name
203         * "mail.<protocol>.class"; if found it attempts to create an instance of the class named in that
204         * property throwing a NoSuchProviderException if the class cannot be loaded.
205         * If this property is not found, it searches the providers returned by {@link #getProviders()}
206         * for a entry for the specified protocol.
207         *
208         * @param protocol the protocol to get a provider for
209         * @return a provider for that protocol
210         * @throws NoSuchProviderException
211         */
212        public Provider getProvider(String protocol) throws NoSuchProviderException {
213            //If we are deployed into an OSGi environment, leverage it
214            Class providerClass = org.apache.servicemix.specs.locator.OsgiLocator.locate(protocol);
215            if (providerClass != null) {
216                try {
217                                    return (Provider) providerClass.newInstance();
218                            } catch (InstantiationException e) {
219                                    throw new NoSuchProviderException(e.getMessage());
220                            } catch (IllegalAccessException e) {
221                                    throw new NoSuchProviderException(e.getMessage());
222                            }
223            }
224    
225            ProviderInfo info = getProviderInfo();
226            Provider provider = null;
227            String providerName = properties.getProperty("mail." + protocol + ".class");
228            if (providerName != null) {
229                provider = (Provider) info.byClassName.get(providerName);
230                if (debug) {
231                    writeDebug("DEBUG: new provider loaded: " + provider.toString());
232                }
233            }
234    
235            // if not able to locate this by class name, just grab a registered protocol.
236            if (provider == null) {
237                provider = (Provider) info.byProtocol.get(protocol);
238            }
239    
240            if (provider == null) {
241                throw new NoSuchProviderException("Unable to locate provider for protocol: " + protocol);
242            }
243            if (debug) {
244                writeDebug("DEBUG: getProvider() returning provider " + provider.toString());
245            }
246            return provider;
247        }
248    
249        /**
250         * Make the supplied Provider the default for its protocol.
251         *
252         * @param provider the new default Provider
253         * @throws NoSuchProviderException
254         */
255        public void setProvider(Provider provider) throws NoSuchProviderException {
256            ProviderInfo info = getProviderInfo();
257            info.byProtocol.put(provider.getProtocol(), provider);
258        }
259    
260        /**
261         * Return a Store for the default protocol defined by the mail.store.protocol property.
262         *
263         * @return the store for the default protocol
264         * @throws NoSuchProviderException
265         */
266        public Store getStore() throws NoSuchProviderException {
267            String protocol = properties.getProperty("mail.store.protocol");
268            if (protocol == null) {
269                throw new NoSuchProviderException("mail.store.protocol property is not set");
270            }
271            return getStore(protocol);
272        }
273    
274        /**
275         * Return a Store for the specified protocol.
276         *
277         * @param protocol the protocol to get a Store for
278         * @return a Store
279         * @throws NoSuchProviderException if no provider is defined for the specified protocol
280         */
281        public Store getStore(String protocol) throws NoSuchProviderException {
282            Provider provider = getProvider(protocol);
283            return getStore(provider);
284        }
285    
286        /**
287         * Return a Store for the protocol specified in the given URL
288         *
289         * @param url the URL of the Store
290         * @return a Store
291         * @throws NoSuchProviderException if no provider is defined for the specified protocol
292         */
293        public Store getStore(URLName url) throws NoSuchProviderException {
294            return (Store) getService(getProvider(url.getProtocol()), url);
295        }
296    
297        /**
298         * Return the Store specified by the given provider.
299         *
300         * @param provider the provider to create from
301         * @return a Store
302         * @throws NoSuchProviderException if there was a problem creating the Store
303         */
304        public Store getStore(Provider provider) throws NoSuchProviderException {
305            if (Provider.Type.STORE != provider.getType()) {
306                throw new NoSuchProviderException("Not a Store Provider: " + provider);
307            }
308            return (Store) getService(provider, null);
309        }
310    
311        /**
312         * Return a closed folder for the supplied URLName, or null if it cannot be obtained.
313         * <p/>
314         * The scheme portion of the URL is used to locate the Provider and create the Store;
315         * the returned Store is then used to obtain the folder.
316         *
317         * @param name the location of the folder
318         * @return the requested folder, or null if it is unavailable
319         * @throws NoSuchProviderException if there is no provider
320         * @throws MessagingException      if there was a problem accessing the Store
321         */
322        public Folder getFolder(URLName name) throws MessagingException {
323            Store store = getStore(name);
324            return store.getFolder(name);
325        }
326    
327        /**
328         * Return a Transport for the default protocol specified by the
329         * <code>mail.transport.protocol</code> property.
330         *
331         * @return a Transport
332         * @throws NoSuchProviderException
333         */
334        public Transport getTransport() throws NoSuchProviderException {
335            String protocol = properties.getProperty("mail.transport.protocol");
336            if (protocol == null) {
337                throw new NoSuchProviderException("mail.transport.protocol property is not set");
338            }
339            return getTransport(protocol);
340        }
341    
342        /**
343         * Return a Transport for the specified protocol.
344         *
345         * @param protocol the protocol to use
346         * @return a Transport
347         * @throws NoSuchProviderException
348         */
349        public Transport getTransport(String protocol) throws NoSuchProviderException {
350            Provider provider = getProvider(protocol);
351            return getTransport(provider);
352        }
353    
354        /**
355         * Return a transport for the protocol specified in the URL.
356         *
357         * @param name the URL whose scheme specifies the protocol
358         * @return a Transport
359         * @throws NoSuchProviderException
360         */
361        public Transport getTransport(URLName name) throws NoSuchProviderException {
362            return (Transport) getService(getProvider(name.getProtocol()), name);
363        }
364    
365        /**
366         * Return a transport for the protocol associated with the type of this address.
367         *
368         * @param address the address we are trying to deliver to
369         * @return a Transport
370         * @throws NoSuchProviderException
371         */
372        public Transport getTransport(Address address) throws NoSuchProviderException {
373            String type = address.getType();
374            // load the address map from the resource files.
375            Map addressMap = getAddressMap();
376            String protocolName = (String)addressMap.get(type);
377            if (protocolName == null) {
378                throw new NoSuchProviderException("No provider for address type " + type);
379            }
380            return getTransport(protocolName);
381        }
382    
383        /**
384         * Return the Transport specified by a Provider
385         *
386         * @param provider the defining Provider
387         * @return a Transport
388         * @throws NoSuchProviderException
389         */
390        public Transport getTransport(Provider provider) throws NoSuchProviderException {
391            return (Transport) getService(provider, null);
392        }
393    
394        /**
395         * Set the password authentication associated with a URL.
396         *
397         * @param name          the url
398         * @param authenticator the authenticator
399         */
400        public void setPasswordAuthentication(URLName name, PasswordAuthentication authenticator) {
401            if (authenticator == null) {
402                passwordAuthentications.remove(name);
403            } else {
404                passwordAuthentications.put(name, authenticator);
405            }
406        }
407    
408        /**
409         * Get the password authentication associated with a URL
410         *
411         * @param name the URL
412         * @return any authenticator for that url, or null if none
413         */
414        public PasswordAuthentication getPasswordAuthentication(URLName name) {
415            return (PasswordAuthentication) passwordAuthentications.get(name);
416        }
417    
418        /**
419         * Call back to the application supplied authenticator to get the needed username add password.
420         *
421         * @param host            the host we are trying to connect to, may be null
422         * @param port            the port on that host
423         * @param protocol        the protocol trying to be used
424         * @param prompt          a String to show as part of the prompt, may be null
425         * @param defaultUserName the default username, may be null
426         * @return the authentication information collected by the authenticator; may be null
427         */
428        public PasswordAuthentication requestPasswordAuthentication(InetAddress host, int port, String protocol, String prompt, String defaultUserName) {
429            if (authenticator == null) {
430                return null;
431            }
432            return authenticator.authenticate(host, port, protocol, prompt, defaultUserName);
433        }
434    
435        /**
436         * Return the properties object for this Session; this is a live collection.
437         *
438         * @return the properties for the Session
439         */
440        public Properties getProperties() {
441            return properties;
442        }
443    
444        /**
445         * Return the specified property.
446         *
447         * @param property the property to get
448         * @return its value, or null if not present
449         */
450        public String getProperty(String property) {
451            return getProperties().getProperty(property);
452        }
453    
454    
455        /**
456         * Add a provider to the Session managed provider list.
457         *
458         * @param provider The new provider to add.
459         */
460        public synchronized void addProvider(Provider provider) {
461            ProviderInfo info = getProviderInfo();
462            info.addProvider(provider);
463        }
464    
465    
466    
467        /**
468         * Add a mapping between an address type and a protocol used
469         * to process that address type.
470         *
471         * @param addressType
472         *                 The address type identifier.
473         * @param protocol The protocol name mapping.
474         */
475        public void setProtocolForAddress(String addressType, String protocol) {
476            Map addressMap = getAddressMap();
477    
478            // no protocol specified is a removal
479            if (protocol == null) {
480                addressMap.remove(addressType);
481            }
482            else {
483                addressMap.put(addressType, protocol);
484            }
485        }
486    
487    
488        private Service getService(Provider provider, URLName name) throws NoSuchProviderException {
489            try {
490                if (name == null) {
491                    name = new URLName(provider.getProtocol(), null, -1, null, null, null); 
492                }
493                
494                //If we are deployed into an OSGi environment, leverage it
495                Class providerClass = org.apache.servicemix.specs.locator.OsgiLocator.locate(provider.getClassName());
496                if (providerClass != null) {
497                    try {
498                            Constructor ctr = providerClass.getConstructor(PARAM_TYPES);
499                        return (Service) ctr.newInstance(new Object[]{this, name});
500                            } catch (InstantiationException e) {
501                                    throw new NoSuchProviderException(e.getMessage());
502                            } catch (IllegalAccessException e) {
503                                    throw new NoSuchProviderException(e.getMessage());
504                            }
505                }
506                
507                ClassLoader cl = getClassLoader();
508                Class clazz = cl.loadClass(provider.getClassName());
509                Constructor ctr = clazz.getConstructor(PARAM_TYPES);
510                return (Service) ctr.newInstance(new Object[]{this, name});
511            } catch (ClassNotFoundException e) {
512                throw (NoSuchProviderException) new NoSuchProviderException("Unable to load class for provider: " + provider).initCause(e);
513            } catch (NoSuchMethodException e) {
514                throw (NoSuchProviderException) new NoSuchProviderException("Provider class does not have a constructor(Session, URLName): " + provider).initCause(e);
515            } catch (InstantiationException e) {
516                throw (NoSuchProviderException) new NoSuchProviderException("Unable to instantiate provider class: " + provider).initCause(e);
517            } catch (IllegalAccessException e) {
518                throw (NoSuchProviderException) new NoSuchProviderException("Unable to instantiate provider class: " + provider).initCause(e);
519            } catch (InvocationTargetException e) {
520                throw (NoSuchProviderException) new NoSuchProviderException("Exception from constructor of provider class: " + provider).initCause(e.getCause());
521            }
522        }
523    
524        private ProviderInfo getProviderInfo() {
525            ClassLoader cl = getClassLoader();
526            ProviderInfo info = (ProviderInfo) providersByClassLoader.get(cl);
527            if (info == null) {
528                info = loadProviders(cl);
529            }
530            return info;
531        }
532    
533        private Map getAddressMap() {
534            ClassLoader cl = getClassLoader();
535            Map addressMap = (Map)addressMapsByClassLoader.get(cl);
536            if (addressMap == null) {
537                addressMap = loadAddressMap(cl);
538            }
539            return addressMap;
540        }
541    
542    
543        /**
544         * Resolve a class loader used to resolve context resources.  The
545         * class loader used is either a current thread context class
546         * loader (if set), the class loader used to load an authenticator
547         * we've been initialized with, or the class loader used to load
548         * this class instance (which may be a subclass of Session).
549         *
550         * @return The class loader used to load resources.
551         */
552        private ClassLoader getClassLoader() {
553            ClassLoader cl = Thread.currentThread().getContextClassLoader();
554            if (cl == null) {
555                if (authenticator != null) {
556                    cl = authenticator.getClass().getClassLoader();
557                }
558                else {
559                    cl = this.getClass().getClassLoader();
560                }
561            }
562            return cl;
563        }
564    
565        private ProviderInfo loadProviders(ClassLoader cl) {
566            // we create a merged map from reading all of the potential address map entries.  The locations
567            // searched are:
568            //   1.   java.home/lib/javamail.address.map
569            //   2. META-INF/javamail.address.map
570            //   3. META-INF/javamail.default.address.map
571            //
572            ProviderInfo info = new ProviderInfo();
573    
574            // make sure this is added to the global map.
575            providersByClassLoader.put(cl, info);
576    
577    
578            // NOTE:  Unlike the addressMap, we process these in the defined order.  The loading routine
579            // will not overwrite entries if they already exist in the map.
580    
581            try {
582                File file = new File(System.getProperty("java.home"), "lib/javamail.providers");
583                InputStream is = new FileInputStream(file);
584                try {
585                    loadProviders(info, is);
586                    if (debug) {
587                        writeDebug("Loaded lib/javamail.providers from " + file.toString());
588                    }
589                } finally{
590                    is.close();
591                }
592            } catch (SecurityException e) {
593                // ignore
594            } catch (IOException e) {
595                // ignore
596            }
597    
598            try {
599                Enumeration e = cl.getResources("META-INF/javamail.providers");
600                while (e.hasMoreElements()) {
601                    URL url = (URL) e.nextElement();
602                    if (debug) {
603                        writeDebug("Loading META-INF/javamail.providers from " + url.toString());
604                    }
605                    InputStream is = url.openStream();
606                    try {
607                        loadProviders(info, is);
608                    } finally{
609                        is.close();
610                    }
611                }
612            } catch (SecurityException e) {
613                // ignore
614            } catch (IOException e) {
615                // ignore
616            }
617    
618            try {
619                Enumeration e = cl.getResources("META-INF/javamail.default.providers");
620                while (e.hasMoreElements()) {
621                    URL url = (URL) e.nextElement();
622                    if (debug) {
623                        writeDebug("Loading javamail.default.providers from " + url.toString());
624                    }
625    
626                    InputStream is = url.openStream();
627                    try {
628                        loadProviders(info, is);
629                    } finally{
630                        is.close();
631                    }
632                }
633            } catch (SecurityException e) {
634                // ignore
635            } catch (IOException e) {
636                // ignore
637            }
638    
639            return info;
640        }
641    
642        private void loadProviders(ProviderInfo info, InputStream is) throws IOException {
643            BufferedReader reader = new BufferedReader(new InputStreamReader(is));
644            String line;
645            while ((line = reader.readLine()) != null) {
646                // Lines beginning with "#" are just comments.  
647                if (line.startsWith("#")) {
648                    continue; 
649                }
650                
651                StringTokenizer tok = new StringTokenizer(line, ";");
652                String protocol = null;
653                Provider.Type type = null;
654                String className = null;
655                String vendor = null;
656                String version = null;
657                while (tok.hasMoreTokens()) {
658                    String property = tok.nextToken();
659                    int index = property.indexOf('=');
660                    if (index == -1) {
661                        continue;
662                    }
663                    String key = property.substring(0, index).trim().toLowerCase();
664                    String value = property.substring(index+1).trim();
665                    if (protocol == null && "protocol".equals(key)) {
666                        protocol = value;
667                    } else if (type == null && "type".equals(key)) {
668                        if ("store".equals(value)) {
669                            type = Provider.Type.STORE;
670                        } else if ("transport".equals(value)) {
671                            type = Provider.Type.TRANSPORT;
672                        }
673                    } else if (className == null && "class".equals(key)) {
674                        className = value;
675                    } else if ("vendor".equals(key)) {
676                        vendor = value;
677                    } else if ("version".equals(key)) {
678                        version = value;
679                    }
680                }
681                if (protocol == null || type == null || className == null) {
682                    //todo should we log a warning?
683                    continue;
684                }
685    
686                if (debug) {
687                    writeDebug("DEBUG: loading new provider protocol=" + protocol + ", className=" + className + ", vendor=" + vendor + ", version=" + version);
688                }
689                Provider provider = new Provider(type, protocol, className, vendor, version);
690                // add to the info list.
691                info.addProvider(provider);
692            }
693        }
694    
695        /**
696         * Load up an address map associated with a using class loader
697         * instance.
698         *
699         * @param cl     The class loader used to resolve the address map.
700         *
701         * @return A map containing the entries associated with this classloader
702         *         instance.
703         */
704        private static Map loadAddressMap(ClassLoader cl) {
705            // we create a merged map from reading all of the potential address map entries.  The locations
706            // searched are:
707            //   1.   java.home/lib/javamail.address.map
708            //   2. META-INF/javamail.address.map
709            //   3. META-INF/javamail.default.address.map
710            //
711            // if all of the above searches fail, we just set up some "default" defaults.
712    
713            // the format of the address.map file is defined as a property file.  We can cheat and
714            // just use Properties.load() to read in the files.
715            Properties addressMap = new Properties();
716    
717            // add this to the tracking map.
718            addressMapsByClassLoader.put(cl, addressMap);
719    
720            // NOTE:  We are reading these resources in reverse order of what's cited above.  This allows
721            // user defined entries to overwrite default entries if there are similarly named items.
722    
723            try {
724                Enumeration e = cl.getResources("META-INF/javamail.default.address.map");
725                while (e.hasMoreElements()) {
726                    URL url = (URL) e.nextElement();
727                    InputStream is = url.openStream();
728                    try {
729                        // load as a property file
730                        addressMap.load(is);
731                    } finally{
732                        is.close();
733                    }
734                }
735            } catch (SecurityException e) {
736                // ignore
737            } catch (IOException e) {
738                // ignore
739            }
740    
741    
742            try {
743                Enumeration e = cl.getResources("META-INF/javamail.address.map");
744                while (e.hasMoreElements()) {
745                    URL url = (URL) e.nextElement();
746                    InputStream is = url.openStream();
747                    try {
748                        // load as a property file
749                        addressMap.load(is);
750                    } finally{
751                        is.close();
752                    }
753                }
754            } catch (SecurityException e) {
755                // ignore
756            } catch (IOException e) {
757                // ignore
758            }
759    
760    
761            try {
762                File file = new File(System.getProperty("java.home"), "lib/javamail.address.map");
763                InputStream is = new FileInputStream(file);
764                try {
765                    // load as a property file
766                    addressMap.load(is);
767                } finally{
768                    is.close();
769                }
770            } catch (SecurityException e) {
771                // ignore
772            } catch (IOException e) {
773                // ignore
774            }
775    
776            try {
777                Enumeration e = cl.getResources("META-INF/javamail.address.map");
778                while (e.hasMoreElements()) {
779                    URL url = (URL) e.nextElement();
780                    InputStream is = url.openStream();
781                    try {
782                        // load as a property file
783                        addressMap.load(is);
784                    } finally{
785                        is.close();
786                    }
787                }
788            } catch (SecurityException e) {
789                // ignore
790            } catch (IOException e) {
791                // ignore
792            }
793    
794    
795            // if unable to load anything, at least create the MimeMessage-smtp protocol mapping.
796            if (addressMap.isEmpty()) {
797                addressMap.put("rfc822", "smtp");
798            }
799    
800            return addressMap;
801        }
802    
803        /**
804         * Private convenience routine for debug output.
805         *
806         * @param msg    The message to write out to the debug stream.
807         */
808        private void writeDebug(String msg) {
809            debugOut.println(msg);
810        }
811    
812    
813        private static class ProviderInfo {
814            private final Map byClassName = new HashMap();
815            private final Map byProtocol = new HashMap();
816            private final List all = new ArrayList();
817    
818            public void addProvider(Provider provider) {
819                String className = provider.getClassName();
820    
821                if (!byClassName.containsKey(className)) {
822                    byClassName.put(className, provider);
823                }
824    
825                String protocol = provider.getProtocol();
826                if (!byProtocol.containsKey(protocol)) {
827                    byProtocol.put(protocol, provider);
828                }
829                all.add(provider);
830            }
831        }
832    }