View Javadoc

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