1 /*
2 * Licensed to the Apache Software Foundation (ASF) under one
3 * or more contributor license agreements. See the NOTICE file
4 * distributed with this work for additional information
5 * regarding copyright ownership. The ASF licenses this file
6 * to you under the Apache License, Version 2.0 (the
7 * "License"); you may not use this file except in compliance
8 * with the License. You may obtain a copy of the License at
9 *
10 * http://www.apache.org/licenses/LICENSE-2.0
11 *
12 * Unless required by applicable law or agreed to in writing,
13 * software distributed under the License is distributed on an
14 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15 * KIND, either express or implied. See the License for the
16 * specific language governing permissions and limitations
17 * under the License.
18 */
19
20 package javax.mail;
21
22 import java.io.BufferedReader;
23 import java.io.File;
24 import java.io.FileInputStream;
25 import java.io.IOException;
26 import java.io.InputStream;
27 import java.io.InputStreamReader;
28 import java.io.PrintStream;
29 import java.lang.reflect.Constructor;
30 import java.lang.reflect.InvocationTargetException;
31 import java.net.InetAddress;
32 import java.net.URL;
33 import java.util.ArrayList;
34 import java.util.Enumeration;
35 import java.util.HashMap;
36 import java.util.List;
37 import java.util.Map;
38 import java.util.Properties;
39 import java.util.StringTokenizer;
40 import java.util.WeakHashMap;
41
42
43 /**
44 * OK, so we have a final class in the API with a heck of a lot of implementation required...
45 * let's try and figure out what it is meant to do.
46 * <p/>
47 * It is supposed to collect together properties and defaults so that they can be
48 * shared by multiple applications on a desktop; with process isolation and no
49 * real concept of shared memory, this seems challenging. These properties and
50 * defaults rely on system properties, making management in a app server harder,
51 * and on resources loaded from "mail.jar" which may lead to skew between
52 * differnet independent implementations of this API.
53 *
54 * @version $Rev: 587072 $ $Date: 2007-10-22 08:38:45 -0400 (Mon, 22 Oct 2007) $
55 */
56 public final class Session {
57 private static final Class[] PARAM_TYPES = {Session.class, URLName.class};
58 private static final WeakHashMap addressMapsByClassLoader = new WeakHashMap();
59 private static Session DEFAULT_SESSION;
60
61 private Map passwordAuthentications = new HashMap();
62
63 private final Properties properties;
64 private final Authenticator authenticator;
65 private boolean debug;
66 private PrintStream debugOut = System.out;
67
68 private static final WeakHashMap providersByClassLoader = new WeakHashMap();
69
70 /**
71 * No public constrcutor allowed.
72 */
73 private Session(Properties properties, Authenticator authenticator) {
74 this.properties = properties;
75 this.authenticator = authenticator;
76 debug = Boolean.valueOf(properties.getProperty("mail.debug")).booleanValue();
77 }
78
79 /**
80 * Create a new session initialized with the supplied properties which uses the supplied authenticator.
81 * Clients should ensure the properties listed in Appendix A of the JavaMail specification are
82 * set as the defaults are unlikey to work in most scenarios; particular attention should be given
83 * to:
84 * <ul>
85 * <li>mail.store.protocol</li>
86 * <li>mail.transport.protocol</li>
87 * <li>mail.host</li>
88 * <li>mail.user</li>
89 * <li>mail.from</li>
90 * </ul>
91 *
92 * @param properties the session properties
93 * @param authenticator an authenticator for callbacks to the user
94 * @return a new session
95 */
96 public static Session getInstance(Properties properties, Authenticator authenticator) {
97 return new Session(new Properties(properties), authenticator);
98 }
99
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 ProviderInfo info = (ProviderInfo) providersByClassLoader.get(cl);
501 if (info == null) {
502 info = loadProviders(cl);
503 }
504 return info;
505 }
506
507 private Map getAddressMap() {
508 ClassLoader cl = getClassLoader();
509 Map addressMap = (Map)addressMapsByClassLoader.get(cl);
510 if (addressMap == null) {
511 addressMap = loadAddressMap(cl);
512 }
513 return addressMap;
514 }
515
516
517 /**
518 * Resolve a class loader used to resolve context resources. The
519 * class loader used is either a current thread context class
520 * loader (if set), the class loader used to load an authenticator
521 * we've been initialized with, or the class loader used to load
522 * this class instance (which may be a subclass of Session).
523 *
524 * @return The class loader used to load resources.
525 */
526 private ClassLoader getClassLoader() {
527 ClassLoader cl = Thread.currentThread().getContextClassLoader();
528 if (cl == null) {
529 if (authenticator != null) {
530 cl = authenticator.getClass().getClassLoader();
531 }
532 else {
533 cl = this.getClass().getClassLoader();
534 }
535 }
536 return cl;
537 }
538
539 private ProviderInfo loadProviders(ClassLoader cl) {
540 // we create a merged map from reading all of the potential address map entries. The locations
541 // searched are:
542 // 1. java.home/lib/javamail.address.map
543 // 2. META-INF/javamail.address.map
544 // 3. META-INF/javamail.default.address.map
545 //
546 ProviderInfo info = new ProviderInfo();
547
548 // make sure this is added to the global map.
549 providersByClassLoader.put(cl, info);
550
551
552 // NOTE: Unlike the addressMap, we process these in the defined order. The loading routine
553 // will not overwrite entries if they already exist in the map.
554
555 try {
556 File file = new File(System.getProperty("java.home"), "lib/javamail.providers");
557 InputStream is = new FileInputStream(file);
558 try {
559 loadProviders(info, is);
560 if (debug) {
561 writeDebug("Loaded lib/javamail.providers from " + file.toString());
562 }
563 } finally{
564 is.close();
565 }
566 } catch (SecurityException e) {
567 // ignore
568 } catch (IOException e) {
569 // ignore
570 }
571
572 try {
573 Enumeration e = cl.getResources("META-INF/javamail.providers");
574 while (e.hasMoreElements()) {
575 URL url = (URL) e.nextElement();
576 if (debug) {
577 writeDebug("Loading META-INF/javamail.providers from " + url.toString());
578 }
579 InputStream is = url.openStream();
580 try {
581 loadProviders(info, is);
582 } finally{
583 is.close();
584 }
585 }
586 } catch (SecurityException e) {
587 // ignore
588 } catch (IOException e) {
589 // ignore
590 }
591
592 try {
593 Enumeration e = cl.getResources("META-INF/javamail.default.providers");
594 while (e.hasMoreElements()) {
595 URL url = (URL) e.nextElement();
596 if (debug) {
597 writeDebug("Loading javamail.default.providers from " + url.toString());
598 }
599
600 InputStream is = url.openStream();
601 try {
602 loadProviders(info, is);
603 } finally{
604 is.close();
605 }
606 }
607 } catch (SecurityException e) {
608 // ignore
609 } catch (IOException e) {
610 // ignore
611 }
612
613 return info;
614 }
615
616 private void loadProviders(ProviderInfo info, InputStream is) throws IOException {
617 BufferedReader reader = new BufferedReader(new InputStreamReader(is));
618 String line;
619 while ((line = reader.readLine()) != null) {
620 // Lines beginning with "#" are just comments.
621 if (line.startsWith("#")) {
622 continue;
623 }
624
625 StringTokenizer tok = new StringTokenizer(line, ";");
626 String protocol = null;
627 Provider.Type type = null;
628 String className = null;
629 String vendor = null;
630 String version = null;
631 while (tok.hasMoreTokens()) {
632 String property = tok.nextToken();
633 int index = property.indexOf('=');
634 if (index == -1) {
635 continue;
636 }
637 String key = property.substring(0, index).trim().toLowerCase();
638 String value = property.substring(index+1).trim();
639 if (protocol == null && "protocol".equals(key)) {
640 protocol = value;
641 } else if (type == null && "type".equals(key)) {
642 if ("store".equals(value)) {
643 type = Provider.Type.STORE;
644 } else if ("transport".equals(value)) {
645 type = Provider.Type.TRANSPORT;
646 }
647 } else if (className == null && "class".equals(key)) {
648 className = value;
649 } else if ("vendor".equals(key)) {
650 vendor = value;
651 } else if ("version".equals(key)) {
652 version = value;
653 }
654 }
655 if (protocol == null || type == null || className == null) {
656 //todo should we log a warning?
657 continue;
658 }
659
660 if (debug) {
661 writeDebug("DEBUG: loading new provider protocol=" + protocol + ", className=" + className + ", vendor=" + vendor + ", version=" + version);
662 }
663 Provider provider = new Provider(type, protocol, className, vendor, version);
664 // add to the info list.
665 info.addProvider(provider);
666 }
667 }
668
669 /**
670 * Load up an address map associated with a using class loader
671 * instance.
672 *
673 * @param cl The class loader used to resolve the address map.
674 *
675 * @return A map containing the entries associated with this classloader
676 * instance.
677 */
678 private static Map loadAddressMap(ClassLoader cl) {
679 // we create a merged map from reading all of the potential address map entries. The locations
680 // searched are:
681 // 1. java.home/lib/javamail.address.map
682 // 2. META-INF/javamail.address.map
683 // 3. META-INF/javamail.default.address.map
684 //
685 // if all of the above searches fail, we just set up some "default" defaults.
686
687 // the format of the address.map file is defined as a property file. We can cheat and
688 // just use Properties.load() to read in the files.
689 Properties addressMap = new Properties();
690
691 // add this to the tracking map.
692 addressMapsByClassLoader.put(cl, addressMap);
693
694 // NOTE: We are reading these resources in reverse order of what's cited above. This allows
695 // user defined entries to overwrite default entries if there are similarly named items.
696
697 try {
698 Enumeration e = cl.getResources("META-INF/javamail.default.address.map");
699 while (e.hasMoreElements()) {
700 URL url = (URL) e.nextElement();
701 InputStream is = url.openStream();
702 try {
703 // load as a property file
704 addressMap.load(is);
705 } finally{
706 is.close();
707 }
708 }
709 } catch (SecurityException e) {
710 // ignore
711 } catch (IOException e) {
712 // ignore
713 }
714
715
716 try {
717 Enumeration e = cl.getResources("META-INF/javamail.address.map");
718 while (e.hasMoreElements()) {
719 URL url = (URL) e.nextElement();
720 InputStream is = url.openStream();
721 try {
722 // load as a property file
723 addressMap.load(is);
724 } finally{
725 is.close();
726 }
727 }
728 } catch (SecurityException e) {
729 // ignore
730 } catch (IOException e) {
731 // ignore
732 }
733
734
735 try {
736 File file = new File(System.getProperty("java.home"), "lib/javamail.address.map");
737 InputStream is = new FileInputStream(file);
738 try {
739 // load as a property file
740 addressMap.load(is);
741 } finally{
742 is.close();
743 }
744 } catch (SecurityException e) {
745 // ignore
746 } catch (IOException e) {
747 // ignore
748 }
749
750 try {
751 Enumeration e = cl.getResources("META-INF/javamail.address.map");
752 while (e.hasMoreElements()) {
753 URL url = (URL) e.nextElement();
754 InputStream is = url.openStream();
755 try {
756 // load as a property file
757 addressMap.load(is);
758 } finally{
759 is.close();
760 }
761 }
762 } catch (SecurityException e) {
763 // ignore
764 } catch (IOException e) {
765 // ignore
766 }
767
768
769 // if unable to load anything, at least create the MimeMessage-smtp protocol mapping.
770 if (addressMap.isEmpty()) {
771 addressMap.put("rfc822", "smtp");
772 }
773
774 return addressMap;
775 }
776
777 /**
778 * Private convenience routine for debug output.
779 *
780 * @param msg The message to write out to the debug stream.
781 */
782 private void writeDebug(String msg) {
783 debugOut.println(msg);
784 }
785
786
787 private static class ProviderInfo {
788 private final Map byClassName = new HashMap();
789 private final Map byProtocol = new HashMap();
790 private final List all = new ArrayList();
791
792 public void addProvider(Provider provider) {
793 String className = provider.getClassName();
794
795 if (!byClassName.containsKey(className)) {
796 byClassName.put(className, provider);
797 }
798
799 String protocol = provider.getProtocol();
800 if (!byProtocol.containsKey(protocol)) {
801 byProtocol.put(protocol, provider);
802 }
803 all.add(provider);
804 }
805 }
806 }