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 }