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 }