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: 702537 $ $Date: 2008-10-07 12:39:34 -0400 (Tue, 07 Oct 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 ProviderInfo info = getProviderInfo();
214 Provider provider = null;
215 String providerName = properties.getProperty("mail." + protocol + ".class");
216 if (providerName != null) {
217 provider = (Provider) info.byClassName.get(providerName);
218 if (debug) {
219 writeDebug("DEBUG: new provider loaded: " + provider.toString());
220 }
221 }
222
223 // if not able to locate this by class name, just grab a registered protocol.
224 if (provider == null) {
225 provider = (Provider) info.byProtocol.get(protocol);
226 }
227
228 if (provider == null) {
229 throw new NoSuchProviderException("Unable to locate provider for protocol: " + protocol);
230 }
231 if (debug) {
232 writeDebug("DEBUG: getProvider() returning provider " + provider.toString());
233 }
234 return provider;
235 }
236
237 /**
238 * Make the supplied Provider the default for its protocol.
239 *
240 * @param provider the new default Provider
241 * @throws NoSuchProviderException
242 */
243 public void setProvider(Provider provider) throws NoSuchProviderException {
244 ProviderInfo info = getProviderInfo();
245 info.byProtocol.put(provider.getProtocol(), provider);
246 }
247
248 /**
249 * Return a Store for the default protocol defined by the mail.store.protocol property.
250 *
251 * @return the store for the default protocol
252 * @throws NoSuchProviderException
253 */
254 public Store getStore() throws NoSuchProviderException {
255 String protocol = properties.getProperty("mail.store.protocol");
256 if (protocol == null) {
257 throw new NoSuchProviderException("mail.store.protocol property is not set");
258 }
259 return getStore(protocol);
260 }
261
262 /**
263 * Return a Store for the specified protocol.
264 *
265 * @param protocol the protocol to get a Store for
266 * @return a Store
267 * @throws NoSuchProviderException if no provider is defined for the specified protocol
268 */
269 public Store getStore(String protocol) throws NoSuchProviderException {
270 Provider provider = getProvider(protocol);
271 return getStore(provider);
272 }
273
274 /**
275 * Return a Store for the protocol specified in the given URL
276 *
277 * @param url the URL of the Store
278 * @return a Store
279 * @throws NoSuchProviderException if no provider is defined for the specified protocol
280 */
281 public Store getStore(URLName url) throws NoSuchProviderException {
282 return (Store) getService(getProvider(url.getProtocol()), url);
283 }
284
285 /**
286 * Return the Store specified by the given provider.
287 *
288 * @param provider the provider to create from
289 * @return a Store
290 * @throws NoSuchProviderException if there was a problem creating the Store
291 */
292 public Store getStore(Provider provider) throws NoSuchProviderException {
293 if (Provider.Type.STORE != provider.getType()) {
294 throw new NoSuchProviderException("Not a Store Provider: " + provider);
295 }
296 return (Store) getService(provider, null);
297 }
298
299 /**
300 * Return a closed folder for the supplied URLName, or null if it cannot be obtained.
301 * <p/>
302 * The scheme portion of the URL is used to locate the Provider and create the Store;
303 * the returned Store is then used to obtain the folder.
304 *
305 * @param name the location of the folder
306 * @return the requested folder, or null if it is unavailable
307 * @throws NoSuchProviderException if there is no provider
308 * @throws MessagingException if there was a problem accessing the Store
309 */
310 public Folder getFolder(URLName name) throws MessagingException {
311 Store store = getStore(name);
312 return store.getFolder(name);
313 }
314
315 /**
316 * Return a Transport for the default protocol specified by the
317 * <code>mail.transport.protocol</code> property.
318 *
319 * @return a Transport
320 * @throws NoSuchProviderException
321 */
322 public Transport getTransport() throws NoSuchProviderException {
323 String protocol = properties.getProperty("mail.transport.protocol");
324 if (protocol == null) {
325 throw new NoSuchProviderException("mail.transport.protocol property is not set");
326 }
327 return getTransport(protocol);
328 }
329
330 /**
331 * Return a Transport for the specified protocol.
332 *
333 * @param protocol the protocol to use
334 * @return a Transport
335 * @throws NoSuchProviderException
336 */
337 public Transport getTransport(String protocol) throws NoSuchProviderException {
338 Provider provider = getProvider(protocol);
339 return getTransport(provider);
340 }
341
342 /**
343 * Return a transport for the protocol specified in the URL.
344 *
345 * @param name the URL whose scheme specifies the protocol
346 * @return a Transport
347 * @throws NoSuchProviderException
348 */
349 public Transport getTransport(URLName name) throws NoSuchProviderException {
350 return (Transport) getService(getProvider(name.getProtocol()), name);
351 }
352
353 /**
354 * Return a transport for the protocol associated with the type of this address.
355 *
356 * @param address the address we are trying to deliver to
357 * @return a Transport
358 * @throws NoSuchProviderException
359 */
360 public Transport getTransport(Address address) throws NoSuchProviderException {
361 String type = address.getType();
362 // load the address map from the resource files.
363 Map addressMap = getAddressMap();
364 String protocolName = (String)addressMap.get(type);
365 if (protocolName == null) {
366 throw new NoSuchProviderException("No provider for address type " + type);
367 }
368 return getTransport(protocolName);
369 }
370
371 /**
372 * Return the Transport specified by a Provider
373 *
374 * @param provider the defining Provider
375 * @return a Transport
376 * @throws NoSuchProviderException
377 */
378 public Transport getTransport(Provider provider) throws NoSuchProviderException {
379 return (Transport) getService(provider, null);
380 }
381
382 /**
383 * Set the password authentication associated with a URL.
384 *
385 * @param name the url
386 * @param authenticator the authenticator
387 */
388 public void setPasswordAuthentication(URLName name, PasswordAuthentication authenticator) {
389 if (authenticator == null) {
390 passwordAuthentications.remove(name);
391 } else {
392 passwordAuthentications.put(name, authenticator);
393 }
394 }
395
396 /**
397 * Get the password authentication associated with a URL
398 *
399 * @param name the URL
400 * @return any authenticator for that url, or null if none
401 */
402 public PasswordAuthentication getPasswordAuthentication(URLName name) {
403 return (PasswordAuthentication) passwordAuthentications.get(name);
404 }
405
406 /**
407 * Call back to the application supplied authenticator to get the needed username add password.
408 *
409 * @param host the host we are trying to connect to, may be null
410 * @param port the port on that host
411 * @param protocol the protocol trying to be used
412 * @param prompt a String to show as part of the prompt, may be null
413 * @param defaultUserName the default username, may be null
414 * @return the authentication information collected by the authenticator; may be null
415 */
416 public PasswordAuthentication requestPasswordAuthentication(InetAddress host, int port, String protocol, String prompt, String defaultUserName) {
417 if (authenticator == null) {
418 return null;
419 }
420 return authenticator.authenticate(host, port, protocol, prompt, defaultUserName);
421 }
422
423 /**
424 * Return the properties object for this Session; this is a live collection.
425 *
426 * @return the properties for the Session
427 */
428 public Properties getProperties() {
429 return properties;
430 }
431
432 /**
433 * Return the specified property.
434 *
435 * @param property the property to get
436 * @return its value, or null if not present
437 */
438 public String getProperty(String property) {
439 return getProperties().getProperty(property);
440 }
441
442
443 /**
444 * Add a provider to the Session managed provider list.
445 *
446 * @param provider The new provider to add.
447 */
448 public synchronized void addProvider(Provider provider) {
449 ProviderInfo info = getProviderInfo();
450 info.addProvider(provider);
451 }
452
453
454
455 /**
456 * Add a mapping between an address type and a protocol used
457 * to process that address type.
458 *
459 * @param addressType
460 * The address type identifier.
461 * @param protocol The protocol name mapping.
462 */
463 public void setProtocolForAddress(String addressType, String protocol) {
464 Map addressMap = getAddressMap();
465
466 // no protocol specified is a removal
467 if (protocol == null) {
468 addressMap.remove(addressType);
469 }
470 else {
471 addressMap.put(addressType, protocol);
472 }
473 }
474
475
476 private Service getService(Provider provider, URLName name) throws NoSuchProviderException {
477 try {
478 if (name == null) {
479 name = new URLName(provider.getProtocol(), null, -1, null, null, null);
480 }
481 ClassLoader cl = getClassLoader();
482 Class clazz = cl.loadClass(provider.getClassName());
483 Constructor ctr = clazz.getConstructor(PARAM_TYPES);
484 return (Service) ctr.newInstance(new Object[]{this, name});
485 } catch (ClassNotFoundException e) {
486 throw (NoSuchProviderException) new NoSuchProviderException("Unable to load class for provider: " + provider).initCause(e);
487 } catch (NoSuchMethodException e) {
488 throw (NoSuchProviderException) new NoSuchProviderException("Provider class does not have a constructor(Session, URLName): " + provider).initCause(e);
489 } catch (InstantiationException e) {
490 throw (NoSuchProviderException) new NoSuchProviderException("Unable to instantiate provider class: " + provider).initCause(e);
491 } catch (IllegalAccessException e) {
492 throw (NoSuchProviderException) new NoSuchProviderException("Unable to instantiate provider class: " + provider).initCause(e);
493 } catch (InvocationTargetException e) {
494 throw (NoSuchProviderException) new NoSuchProviderException("Exception from constructor of provider class: " + provider).initCause(e.getCause());
495 }
496 }
497
498 private ProviderInfo getProviderInfo() {
499 ClassLoader cl = getClassLoader();
500 synchronized (providersByClassLoader) {
501 ProviderInfo info = (ProviderInfo) providersByClassLoader.get(cl);
502 if (info == null) {
503 info = loadProviders(cl);
504 }
505 return info;
506 }
507 }
508
509 private Map getAddressMap() {
510 ClassLoader cl = getClassLoader();
511 Map addressMap = (Map)addressMapsByClassLoader.get(cl);
512 if (addressMap == null) {
513 addressMap = loadAddressMap(cl);
514 }
515 return addressMap;
516 }
517
518
519 /**
520 * Resolve a class loader used to resolve context resources. The
521 * class loader used is either a current thread context class
522 * loader (if set), the class loader used to load an authenticator
523 * we've been initialized with, or the class loader used to load
524 * this class instance (which may be a subclass of Session).
525 *
526 * @return The class loader used to load resources.
527 */
528 private ClassLoader getClassLoader() {
529 ClassLoader cl = Thread.currentThread().getContextClassLoader();
530 if (cl == null) {
531 if (authenticator != null) {
532 cl = authenticator.getClass().getClassLoader();
533 }
534 else {
535 cl = this.getClass().getClassLoader();
536 }
537 }
538 return cl;
539 }
540
541 private ProviderInfo loadProviders(ClassLoader cl) {
542 // we create a merged map from reading all of the potential address map entries. The locations
543 // searched are:
544 // 1. java.home/lib/javamail.address.map
545 // 2. META-INF/javamail.address.map
546 // 3. META-INF/javamail.default.address.map
547 //
548 ProviderInfo info = new ProviderInfo();
549
550 // NOTE: Unlike the addressMap, we process these in the defined order. The loading routine
551 // will not overwrite entries if they already exist in the map.
552
553 try {
554 File file = new File(System.getProperty("java.home"), "lib/javamail.providers");
555 InputStream is = new FileInputStream(file);
556 try {
557 loadProviders(info, is);
558 if (debug) {
559 writeDebug("Loaded lib/javamail.providers from " + file.toString());
560 }
561 } finally{
562 is.close();
563 }
564 } catch (SecurityException e) {
565 // ignore
566 } catch (IOException e) {
567 // ignore
568 }
569
570 try {
571 Enumeration e = cl.getResources("META-INF/javamail.providers");
572 while (e.hasMoreElements()) {
573 URL url = (URL) e.nextElement();
574 if (debug) {
575 writeDebug("Loading META-INF/javamail.providers from " + url.toString());
576 }
577 InputStream is = url.openStream();
578 try {
579 loadProviders(info, is);
580 } finally{
581 is.close();
582 }
583 }
584 } catch (SecurityException e) {
585 // ignore
586 } catch (IOException e) {
587 // ignore
588 }
589
590 try {
591 Enumeration e = cl.getResources("META-INF/javamail.default.providers");
592 while (e.hasMoreElements()) {
593 URL url = (URL) e.nextElement();
594 if (debug) {
595 writeDebug("Loading javamail.default.providers from " + url.toString());
596 }
597
598 InputStream is = url.openStream();
599 try {
600 loadProviders(info, is);
601 } finally{
602 is.close();
603 }
604 }
605 } catch (SecurityException e) {
606 // ignore
607 } catch (IOException e) {
608 // ignore
609 }
610
611 // make sure this is added to the global map.
612 providersByClassLoader.put(cl, info);
613
614 return info;
615 }
616
617 private void loadProviders(ProviderInfo info, InputStream is) throws IOException {
618 BufferedReader reader = new BufferedReader(new InputStreamReader(is));
619 String line;
620 while ((line = reader.readLine()) != null) {
621 // Lines beginning with "#" are just comments.
622 if (line.startsWith("#")) {
623 continue;
624 }
625
626 StringTokenizer tok = new StringTokenizer(line, ";");
627 String protocol = null;
628 Provider.Type type = null;
629 String className = null;
630 String vendor = null;
631 String version = null;
632 while (tok.hasMoreTokens()) {
633 String property = tok.nextToken();
634 int index = property.indexOf('=');
635 if (index == -1) {
636 continue;
637 }
638 String key = property.substring(0, index).trim().toLowerCase();
639 String value = property.substring(index+1).trim();
640 if (protocol == null && "protocol".equals(key)) {
641 protocol = value;
642 } else if (type == null && "type".equals(key)) {
643 if ("store".equals(value)) {
644 type = Provider.Type.STORE;
645 } else if ("transport".equals(value)) {
646 type = Provider.Type.TRANSPORT;
647 }
648 } else if (className == null && "class".equals(key)) {
649 className = value;
650 } else if ("vendor".equals(key)) {
651 vendor = value;
652 } else if ("version".equals(key)) {
653 version = value;
654 }
655 }
656 if (protocol == null || type == null || className == null) {
657 //todo should we log a warning?
658 continue;
659 }
660
661 if (debug) {
662 writeDebug("DEBUG: loading new provider protocol=" + protocol + ", className=" + className + ", vendor=" + vendor + ", version=" + version);
663 }
664 Provider provider = new Provider(type, protocol, className, vendor, version);
665 // add to the info list.
666 info.addProvider(provider);
667 }
668 }
669
670 /**
671 * Load up an address map associated with a using class loader
672 * instance.
673 *
674 * @param cl The class loader used to resolve the address map.
675 *
676 * @return A map containing the entries associated with this classloader
677 * instance.
678 */
679 private static Map loadAddressMap(ClassLoader cl) {
680 // we create a merged map from reading all of the potential address map entries. The locations
681 // searched are:
682 // 1. java.home/lib/javamail.address.map
683 // 2. META-INF/javamail.address.map
684 // 3. META-INF/javamail.default.address.map
685 //
686 // if all of the above searches fail, we just set up some "default" defaults.
687
688 // the format of the address.map file is defined as a property file. We can cheat and
689 // just use Properties.load() to read in the files.
690 Properties addressMap = new Properties();
691
692 // add this to the tracking map.
693 addressMapsByClassLoader.put(cl, addressMap);
694
695 // NOTE: We are reading these resources in reverse order of what's cited above. This allows
696 // user defined entries to overwrite default entries if there are similarly named items.
697
698 try {
699 Enumeration e = cl.getResources("META-INF/javamail.default.address.map");
700 while (e.hasMoreElements()) {
701 URL url = (URL) e.nextElement();
702 InputStream is = url.openStream();
703 try {
704 // load as a property file
705 addressMap.load(is);
706 } finally{
707 is.close();
708 }
709 }
710 } catch (SecurityException e) {
711 // ignore
712 } catch (IOException e) {
713 // ignore
714 }
715
716
717 try {
718 Enumeration e = cl.getResources("META-INF/javamail.address.map");
719 while (e.hasMoreElements()) {
720 URL url = (URL) e.nextElement();
721 InputStream is = url.openStream();
722 try {
723 // load as a property file
724 addressMap.load(is);
725 } finally{
726 is.close();
727 }
728 }
729 } catch (SecurityException e) {
730 // ignore
731 } catch (IOException e) {
732 // ignore
733 }
734
735
736 try {
737 File file = new File(System.getProperty("java.home"), "lib/javamail.address.map");
738 InputStream is = new FileInputStream(file);
739 try {
740 // load as a property file
741 addressMap.load(is);
742 } finally{
743 is.close();
744 }
745 } catch (SecurityException e) {
746 // ignore
747 } catch (IOException e) {
748 // ignore
749 }
750
751 try {
752 Enumeration e = cl.getResources("META-INF/javamail.address.map");
753 while (e.hasMoreElements()) {
754 URL url = (URL) e.nextElement();
755 InputStream is = url.openStream();
756 try {
757 // load as a property file
758 addressMap.load(is);
759 } finally{
760 is.close();
761 }
762 }
763 } catch (SecurityException e) {
764 // ignore
765 } catch (IOException e) {
766 // ignore
767 }
768
769
770 // if unable to load anything, at least create the MimeMessage-smtp protocol mapping.
771 if (addressMap.isEmpty()) {
772 addressMap.put("rfc822", "smtp");
773 }
774
775 return addressMap;
776 }
777
778 /**
779 * Private convenience routine for debug output.
780 *
781 * @param msg The message to write out to the debug stream.
782 */
783 private void writeDebug(String msg) {
784 debugOut.println(msg);
785 }
786
787
788 private static class ProviderInfo {
789 private final Map byClassName = new HashMap();
790 private final Map byProtocol = new HashMap();
791 private final List all = new ArrayList();
792
793 public void addProvider(Provider provider) {
794 String className = provider.getClassName();
795
796 if (!byClassName.containsKey(className)) {
797 byClassName.put(className, provider);
798 }
799
800 String protocol = provider.getProtocol();
801 if (!byProtocol.containsKey(protocol)) {
802 byProtocol.put(protocol, provider);
803 }
804 all.add(provider);
805 }
806 }
807 }