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: 587072 $ $Date: 2007-10-22 08:38:45 -0400 (Mon, 22 Oct 2007) $ 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 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 }