001    /**
002     *
003     * Copyright 2003-2006 The Apache Software Foundation
004     *
005     *  Licensed under the Apache License, Version 2.0 (the "License");
006     *  you may not use this file except in compliance with the License.
007     *  You may obtain a copy of the License at
008     *
009     *     http://www.apache.org/licenses/LICENSE-2.0
010     *
011     *  Unless required by applicable law or agreed to in writing, software
012     *  distributed under the License is distributed on an "AS IS" BASIS,
013     *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
014     *  See the License for the specific language governing permissions and
015     *  limitations under the License.
016     */
017    
018    package javax.mail;
019    
020    import java.io.BufferedReader;
021    import java.io.File;
022    import java.io.FileInputStream;
023    import java.io.IOException;
024    import java.io.InputStream;
025    import java.io.InputStreamReader;
026    import java.io.PrintStream;
027    import java.lang.reflect.Constructor;
028    import java.lang.reflect.InvocationTargetException;
029    import java.net.InetAddress;
030    import java.net.URL;
031    import java.util.ArrayList;
032    import java.util.Enumeration;
033    import java.util.HashMap;
034    import java.util.List;
035    import java.util.Map;
036    import java.util.Properties;
037    import java.util.StringTokenizer;
038    import java.util.WeakHashMap;
039    
040    
041    /**
042     * OK, so we have a final class in the API with a heck of a lot of implementation required...
043     * let's try and figure out what it is meant to do.
044     * <p/>
045     * It is supposed to collect together properties and defaults so that they can be
046     * shared by multiple applications on a desktop; with process isolation and no
047     * real concept of shared memory, this seems challenging. These properties and
048     * defaults rely on system properties, making management in a app server harder,
049     * and on resources loaded from "mail.jar" which may lead to skew between
050     * differnet independent implementations of this API.
051     *
052     * @version $Rev: 421852 $ $Date: 2006-07-14 03:02:19 -0700 (Fri, 14 Jul 2006) $
053     */
054    public final class Session {
055        private static final Class[] PARAM_TYPES = {Session.class, URLName.class};
056        private static final WeakHashMap addressMapsByClassLoader = new WeakHashMap();
057        private static Session DEFAULT_SESSION;
058    
059        private Map passwordAuthentications = new HashMap();
060    
061        private final Properties properties;
062        private final Authenticator authenticator;
063        private boolean debug;
064        private PrintStream debugOut = System.out;
065    
066        private static final WeakHashMap providersByClassLoader = new WeakHashMap();
067    
068        /**
069         * No public constrcutor allowed.
070         */
071        private Session(Properties properties, Authenticator authenticator) {
072            this.properties = properties;
073            this.authenticator = authenticator;
074            debug = Boolean.valueOf(properties.getProperty("mail.debug")).booleanValue();
075        }
076    
077        /**
078         * Create a new session initialized with the supplied properties which uses the supplied authenticator.
079         * Clients should ensure the properties listed in Appendix A of the JavaMail specification are
080         * set as the defaults are unlikey to work in most scenarios; particular attention should be given
081         * to:
082         * <ul>
083         * <li>mail.store.protocol</li>
084         * <li>mail.transport.protocol</li>
085         * <li>mail.host</li>
086         * <li>mail.user</li>
087         * <li>mail.from</li>
088         * </ul>
089         *
090         * @param properties    the session properties
091         * @param authenticator an authenticator for callbacks to the user
092         * @return a new session
093         */
094        public static Session getInstance(Properties properties, Authenticator authenticator) {
095            return new Session(new Properties(properties), authenticator);
096        }
097    
098        /**
099         * 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    }